为什么PHP允许“不兼容”的构造函数?

2022-08-30 15:15:12

以下是几个片段:

  1. 重写构造函数方法具有一个额外的参数。

    class Cat {
        function __construct() {}
    }
    
    class Lion extends Cat {
        function __construct($param) {}
    }
    
  2. 重写(常规)方法具有一个额外的参数。

    class Cat {
        function doSomething() {}
    }
    
    class Lion extends Cat {
        function doSomething($param) {}
    }
    

第一个将起作用,而第二个将投掷 .Declaration of Lion::doSomething() should be compatible with that of Cat::doSomething()

为什么对构造函数方法有特殊的态度?


答案 1

要理解为什么它们被区别对待,你必须理解Liskov的替代原理,该原理指出

如果对于S类型的每个对象o1,都有一个T类型的对象o2,使得对于所有根据T定义的程序P,当o1被替换为o2时,P的行为保持不变那么S就是T的子类型。

简而言之,这意味着任何使用您的或应该能够可靠地调用它的类,无论该类是一个还是另一个。如果更改方法签名,则无法再保证这一点(您可以加宽它,但不能缩小它)。LionCatdoSomething

非常简单的例子

public function doSomethingWithFeline(Cat $feline)
{
    $feline->doSomething(42);
}

由于,您建立了一个 is-a 关系,这意味着将接受 a for a 。现在想象一下,您在 中添加一个必需的参数。上面的代码会中断,因为它没有传递该新参数。因此,需要兼容的签名。Lion extends CatdoSomethingWithFelineLionCatdoSomethingLion

但是,LSP 不适用于构造函数因为子类型可能具有不同的依赖项。例如,如果您有一个FileLogger和一个DBLogger,则第一个的ctor(构造函数)将需要文件名,而后者则需要db适配器。因此,ctor 是关于具体实现的,而不是类之间契约的一部分。


答案 2

里氏替换原理指出,“如果S是T的子类型,那么T类型的对象可以用S类型的对象替换”。在您的示例中,这意味着您应该能够将类型的对象替换为 类型的对象。CatLion

这就是不允许使用第二个代码的原因。您将无法再执行此替换,因为您将无法再调用没有参数的方法。->doSomething()

另一方面,构造函数不受Liskov替换原则的约束,因为它不是生成的对象API的一部分。您仍然可以替换生成的对象,而不管构造函数签名是否匹配。

实际上,子类具有更多构造函数参数是很常见的,因为它们更具体并且需要更多的依赖项。


推荐