PHP 中使用组合优先于继承的原因
在面向对象编程中,类可以通过两种方式实现多态行为:“继承”和“组合”。
继承是一种将对象或类建立在另一个对象或类的基础上并保留类似实现的机制。换句话说,使用继承,类从其父类(无论是直接的还是间接的)继承字段和方法。子类可以覆盖它继承的方法,也可以隐藏它继承的字段或方法。继承在类之间建立了一种“is-a”关系。例如,“汽车就是车辆”。意思是汽车“继承”了汽车的所有特性。
另一方面,组合(Composition)是通过包含实现所需功能的其他类的实例来跨类重用代码的机制。组合在类之间建立的是“has-A”关系。例如,“汽车有发动机”。有几个原因支持使用组合而不是继承。让我们逐一复习一下。
可维护性及松耦合
让我们通过一个例子来来理解继承和组合,并分析为什么实现继承是危险的。
<?php
class Vehicle
{
public function move()
{
echo "Move the car";
}
}
class Car extends Vehicle
{
public function accelarate()
{
$this->move();
}
}
$car = new Car();
$car->accelarate(); //Move the car
在上面的例子中,我们已经 Car
类继承了 Vehicle
类中。这使得 Vehicle
和 Car
之间的耦合非常紧密。如果 Vehicle
类中发生了任何更改,特别是在 move()
方法中,Car
类可以很容易地被破坏,因为超类 Vehicle
不知道它的子类是用来做什么的。
这种紧耦合可以通过组合还缓解。让我们来修改上例看看如何通过组合解决该问题。
<?php
class Vehicle
{
public function move()
{
echo "Move the car";
}
}
class Car
{
private $vehicle;
public function __construct(Vehicle $vehicle)
{
$this->vehicle = $vehicle;
}
public function accelarate()
{
$this->vehicle->move();
}
}
$vehicle = new Vehicle();
$car = new Car($vehicle);
$car->accelarate(); //Move the car
正如在上面的例子中看到的,我们不再使用继承。相反,我们现在使用组合来实现我们需要的目标。在这里,我们现在使用依赖注入将 Vehicle
类的引用传递到 Car
类的构造函数中。所以,我们不完全依赖 Vehicle
类,因为我们可以很容易地将其换成另一个类。因此没有紧密耦合。现在,超类和子类是高度独立的。类可以自由地进行在继承情况下很危险的更改。
可测试性更好
<?php
class Vehicle
{
public function move()
{
echo "Move the car";
}
}
class Car
{
private $vehicle;
public function __construct(Vehicle $vehicle)
{
$this->vehicle = $vehicle;
}
public function accelarate()
{
$this->vehicle->move();
}
}
在上面的例子中,如果我们没有 Vehicle
类的实例,那么可以通过使用一些测试数据轻松地对其进行模拟,并且可以轻松地测试所有方法。这在继承中根本不可能,因为你严重依赖于超类来获取实例的状态并执行任何方法。
对缺乏多继承的修复
使用组合,像 PHP 这样的单继承语言,可以很容易客服缺乏多继承的缺点。如下示例:
<?php
class Vehicle
{
public function move()
{
echo "Move the car";
}
}
class Tire
{
public function addAlloys()
{
echo "Adding alloy wheels...";
}
}
class Car
{
private $vehicle;
private $tire;
public function __construct(Vehicle $vehicle, Tire $tire)
{
$this->vehicle = $vehicle;
$this->tire = $tire;
}
public function accelarate()
{
$this->vehicle->move();
$this->tire->addAlloys();
}
}
如你所见,我们已经调整了前面的例子,在类 Car
中使用另一个类 Tyre
。所以,我们现在在 Car
类中使用两个不同的类。这在继承的情况下是不可能的。尤其是当语言不支持多重继承时。
结论
在使用继承之前,请考虑组合是否更为合理。子类通常意味着更复杂和更连通,即更难在不出错的情况下更改、维护和扩展。在大多数情况下,组合模式可以取代继承。