PHP 之高级面向对象编程 深入理解设计模式、原则与性能优化
面向对象编程(Object-Oriented Programming, OOP)不只是“如何写 class”的语法规则。它更像一种组织软件系统的思维方式:通过清晰的边界、职责拆分与对象协作,让系统更容易理解、扩展和维护。
当你已经掌握了类、对象、属性、方法这些基础概念之后,就可以把视角往更深一层挪一挪:设计模式、SOLID 原则,以及在大型系统里绕不开的性能与内存问题。
本文会围绕几个常见主题展开:策略模式与单例模式、SOLID 五原则,以及不可变对象与内存管理相关的性能考虑。它们的目的不是“炫技”,而是让真实项目在后续迭代中更稳、更好改。
原文链接 PHP 之高级面向对象编程 深入理解设计模式、原则与性能优化
面向对象编程中的设计模式
设计模式是对软件设计中常见问题的可复用解决方案。它们不是代码模板,而是可以在不同场景下调整和落地的一组设计思路。
策略模式:将算法与类解耦
策略模式(Strategy Pattern)可以把一组可互换的算法抽出来,用统一的接口对外暴露,让使用方在运行时自由选择实现,而不需要把具体算法硬编码进业务类。
当你希望“根据条件切换算法”,又不希望让一个类膨胀到塞满 if/else 时,策略模式通常很好用。
以电商折扣为例:可能有“打折”“买一送一”等不同折扣策略。如果把所有折扣规则都堆在 Cart 里,Cart 会越来越难维护。用策略模式则可以把折扣计算拆到不同策略类里。
<?phpinterface DiscountStrategy {public function apply(float $total): float;
}class PercentDiscount implements DiscountStrategy {public function __construct(private float $percent) {}public function apply(float $total): float {return $total * (1 - $this->percent);}
}class BuyOneGetOneFree implements DiscountStrategy {public function apply(float $total): float {// 简单模拟:买一送一即总价减半return $total / 2;}
}class Cart {private array $items = [];// 依赖注入:注入策略接口而非具体实现public function __construct(private DiscountStrategy $strategy) {}public function addItem(float $price): void {$this->items[] = $price;}public function total(): float {$subtotal = array_sum($this->items);return $this->strategy->apply($subtotal);}
}// 使用示例
$cart = new Cart(new PercentDiscount(0.1)); // 打九折
$cart->addItem(100);
$cart->addItem(200);
echo $cart->total(); // 输出: 270
这里 Cart 不需要知道“折扣怎么算”,它只依赖一个抽象的 DiscountStrategy。想更换折扣规则,只要传入不同的策略实现即可,无需修改 Cart。
单例模式:确保只有一个实例
单例模式(Singleton Pattern)用于保证某个类在应用生命周期内只有一个实例,并提供一个全局访问点。
它常用于管理共享资源,例如数据库连接、配置对象等:这类对象通常不希望被频繁创建,也不希望出现多个实例导致状态不一致。
<?phpclass DatabaseConnection {private static ?DatabaseConnection $instance = null;// 私有化构造函数,防止外部 newprivate function __construct() {echo "连接到数据库...\n";}// 防止克隆private function __clone() {}// 防止反序列化public function __wakeup() {throw new \Exception("Cannot unserialize singleton");}public static function getInstance(): DatabaseConnection {if (self::$instance === null) {self::$instance = new self();}return self::$instance;}
}// 使用示例
$db1 = DatabaseConnection::getInstance();
$db2 = DatabaseConnection::getInstance();var_dump($db1 === $db2); // 输出: bool(true)
上面 DatabaseConnection 通过重写 __new__ 来确保无论实例化多少次,返回的都是同一个对象。
SOLID 原则
SOLID 是一组面向对象设计原则,目标是让系统更易维护、可扩展、可测试。把这些原则落到日常编码里,能减少很多常见的“面向对象写着写着就变成一团”的问题。
单一职责原则(SRP)
单一职责原则(Single Responsibility Principle)强调:一个类应该只有一个引起它变化的原因。换句话说,一个类最好只负责一件事。
例如 User 既保存用户数据又负责发送通知,会同时承担“数据模型”和“通知逻辑”两种职责。可以按 SRP 拆分:
<?phpclass User {public function __construct(public string $name) {}
}class UserNotification {public function sendWelcomeEmail(User $user): void {echo "正在向 {$user->name} 发送欢迎邮件。\n";}
}// 使用
$user = new User("张三");
$notifier = new UserNotification();
$notifier->sendWelcomeEmail($user);
拆分之后,每个类的职责更集中,测试与修改也更可控。
开放/封闭原则(OCP)
开放/封闭原则(Open/Closed Principle)要求:对扩展开放,对修改封闭。也就是在不改动既有代码的前提下,能够通过扩展来引入新行为。
下面用形状面积示例说明:Shape 定义接口,不同形状通过继承实现 area(),调用方不需要知道具体类型。
<?phpinterface Shape {public function area(): float;
}class Circle implements Shape {public function __construct(private float $radius) {}public function area(): float {return pi() * pow($this->radius, 2);}
}class Rectangle implements Shape {public function __construct(private float $width, private float $height) {}public function area(): float {return $this->width * $this->height;}
}function printArea(Shape $shape) {echo "面积: " . $shape->area() . "\n";
}// 使用
printArea(new Circle(5));
printArea(new Rectangle(4, 6));
当你要新增一种形状时,只需要添加新子类即可,不必改动 print_area 或既有形状代码。
里氏替换原则(LSP)
里氏替换原则(Liskov Substitution Principle)指出:基类能出现的地方,子类也应该能替换进去,并且不影响程序正确性。它要求子类是对父类能力的“真实扩展”,而不是“表面继承”。
下面这个例子里,Penguin 继承 Bird 但无法飞行,导致替换失败:
<?phpclass Bird {public function fly(): void {echo "正在飞行...\n";}
}class Penguin extends Bird {public function fly(): void {// 企鹅不会飞,违反了父类 Bird 的行为预期throw new Exception("企鹅不会飞!");}
}/** * 修正方案:拆分接口* interface Bird {}* interface FlyingBird extends Bird { public function fly(); }* class Penguin implements Bird { ... }*/
这个设计违反了 LSP:Penguin 无法在需要 Bird.fly() 的上下文中正常工作。要解决它,通常需要重新审视抽象(例如把“会飞”能力从 Bird 中拆出来,或者调整继承层次)。
接口隔离原则(ISP)
接口隔离原则(Interface Segregation Principle)强调:客户端不应该被迫依赖自己用不到的方法。与其做一个“很大很全”的接口,不如拆成多个小而聚焦的接口,让使用方按需组合。
<?phpinterface Workable {public function work(): void;
}interface Eatable {public function eat(): void;
}class HumanWorker implements Workable, Eatable {public function work(): void { echo "人类在工作\n"; }public function eat(): void { echo "人类在吃饭\n"; }
}class RobotWorker implements Workable {public function work(): void { echo "机器人在工作\n"; }// 机器人不需要实现 eat() 接口
}
这里 Worker 与 Eater 拆成两个接口,避免了让所有实现者都背上不需要的职责。
依赖倒置原则(DIP)
依赖倒置原则(Dependency Inversion Principle)主张:高层模块不应该依赖低层模块,二者都应该依赖抽象(接口/抽象类)。这样可以降低耦合,让替换实现更容易。
<?phpinterface Switchable {public function turnOn(): void;public function turnOff(): void;
}class LightBulb implements Switchable {public function turnOn(): void { echo "灯亮了\n"; }public function turnOff(): void { echo "灯灭了\n"; }
}class Switcher {// 依赖于接口,而不是具体的 LightBulb 类public function __construct(private Switchable $device) {}public function operate(): void {$this->device->turnOn();}
}$bulb = new LightBulb();
$switch = new Switcher($bulb);
$switch->operate();
这里 Switch 直接依赖 LightBulb。如果把依赖改为抽象接口(比如 Switchable),Switch 就能支持更多设备实现,而不需要改动自身代码。
面向对象编程中的性能优化与内存考量
OOP 通常更强调清晰与可维护,但在大型系统里,性能与内存开销同样需要被考虑。理解对象创建、内存分配以及不可变性带来的影响,有助于在“可读性”和“效率”之间取得更好的平衡。
为值对象引入不可变性
PHP 8.2 引入了 readonly class,这非常适合实现不可变的值对象。不可变对象在处理值类型(Value Object)时很常见,例如 Money、DateRange、Coordinate。把对象设计成不可变,可以减少共享状态带来的副作用,也更利于推理与并发场景下的安全性。
<?phpreadonly class Money {public function __construct(public string $currency,public int $amount) {}public function add(Money $other): Money {if ($this->currency !== $other->currency) {throw new InvalidArgumentException("币种不匹配");}// 返回新实例,而不是修改当前对象return new Money($this->currency, $this->amount + $other->amount);}
}$m1 = new Money("CNY", 100);
$m2 = new Money("CNY", 50);
$result = $m1->add($m2);echo $result->amount; // 150
// $m1->amount = 200; // 会报错,因为是 readonly
在这个例子里,Money 是不可变的:每次运算都会返回一个新实例,原对象保持不变。
避免对象抖动(Object Churn)
在紧密循环里频繁创建和销毁对象,会带来明显的性能问题:更多的内存分配、更高的垃圾回收(GC)压力。
遇到这种情况,可以考虑对象池(object pool)或复用对象,减少不必要的创建与回收开销。
总结:把原则与模式落到代码里
面向对象编程提供了一套组织复杂系统的工具。设计模式(例如 Strategy、Singleton)帮助你复用成熟的结构;SOLID 原则提醒你如何划分职责、控制依赖;而不可变对象、对象创建成本等性能视角则让系统在规模化场景下更稳。
OOP 的重点不在“有多少类”,而在抽象是否清晰、边界是否明确、实现是否可替换、变化是否被限制在可控的范围内。把这些要点坚持下来,代码通常会更好读,也更好维护。