为什么PHP允许“不兼容”的构造函数?
为什么PHP允许“不兼容”的构造函数?
以下是一些代码片段:
- 覆盖构造方法有一个额外的参数。
class Cat { function __construct() {} } class Lion extends Cat { function __construct($param) {} }
- 覆盖(常规)方法有一个额外的参数。
class Cat { function doSomething() {} } class Lion extends Cat { function doSomething($param) {} }
第一个会有效,而第二个会抛出Declaration of Lion::doSomething() should be compatible with that of Cat::doSomething()
。
为什么对构造方法有特殊的态度呢?
里氏替换原则(Liskov Substitution Principle)指出“如果 S 是 T 的子类型,那么类型 T 的对象可以替换为类型 S 的对象”。在你的例子中,这意味着你应该能够用类型为 Cat
的对象替换类型为 Lion
的对象。
这就是为什么你的第二段代码是不允许的。您将不再能够进行此替换,因为您将不再能够调用没有参数的 ->doSomething()
方法。
另一方面,构造函数不受里氏替换原则的约束,因为它不是生成对象的 API 的一部分。无论构造函数签名是否匹配,您仍将能够替换生成的对象。
实际上,子类具有更多构造函数参数是很常见的,因为它们更具体,并且需要更多依赖项。
要理解它们为什么被处理不同,你必须理解Liskov替换原则,它表明:
如果对于类型S的每个对象o1都有一个类型T的对象o2,以便对于所有以T为基础定义的程序P,在o1替换o2时,P的行为不变,则S是T的子类型。——Barbara Liskov,《数据抽象与层次结构,SIGPLAN通知,23,5(1988年5月)。
简而言之,这意味着使用你的Lion
或Cat
的任何类都应该能够可靠地在其上调用doSomething
,无论该类是哪一个。如果您更改了方法签名,则不能再保证这一点(尽管您可以扩大它,但不可以缩小它)。
非常简单的例子
public function doSomethingWithFeline(Cat $feline) { $feline->doSomething(42); }
由于Lion extends Cat
,您建立了一种is-a关系,这意味着doSomethingWithFeline
将接受一个Lion
作为Cat
。现在想象一下,在Lion
中添加了一个必需的参数到doSomething
中。上面的代码将会报错,因为它没有传递那个新参数。因此,需要兼容的签名。
LSP不适用于构造函数,因为子类型可能具有不同的依赖关系。例如,如果你有一个FileLogger和一个DBLogger,第一个的构造函数将需要一个文件名,而后者将需要一个db适配器。因此,构造函数是关于具体实现而不是类之间的契约的。