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

Ema*_*sev 27 php oop constructor

这里有几个片段:

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

    class Cat {
        function __construct() {}
    }
    
    class Lion extends Cat {
        function __construct($param) {}
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 覆盖(常规)方法有一个额外的参数.

    class Cat {
        function doSomething() {}
    }
    
    class Lion extends Cat {
        function doSomething($param) {}
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

Gor*_*don 34

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

如果对于类型S的每个对象o1,存在类型为T的对象o2,使得对于以T表示的所有程序P,当o1代替o2时P的行为不变,则S是T的子类型." - BarbaraLiskov,数据抽象和层次结构,SIGPLAN Notices,23,5(1988年5月).

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

很简单的例子

public function doSomethingWithFeline(Cat $feline)
{
    $feline->doSomething(42);
}
Run Code Online (Sandbox Code Playgroud)

既然Lion extends Cat,你建立了一个is-a关系,意思是doSomethingWithFeline接受a Lion为a Cat.现在想象你添加所需的参数doSomethingLion.上面的代码会破坏,因为它没有传递新的参数.因此,需要兼容的签名.

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


Nik*_*kiC 12

里氏替换原则指出"如果S是T的子类型,则类型T的对象可以与S型的对象替换".在您的示例中,这意味着您应该能够使用类型Cat的对象替换类型的对象Lion.

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

在另一方面的构造是受里氏替换原则,因为它不是生成的对象API的一部分.无论构造函数签名是否匹配,您仍然可以替换生成的对象.

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


Mik*_*osh 5

__construct(),因为它们是每个类唯一的.对于构造函数Lion是不是一样的构造Cat,但如果Lion延伸Cat,并Lion没有一个__construct(),你仍然可以扩展父__construct()Lion::__construct().

与其他方法不同,当使用与父__construct()方法不同的参数覆盖__construct()时,PHP不会生成E_STRICT级错误消息.

PHP手册:构造函数和析构函数

其他魔术方法采用特定的参数,这意味着他们的参数计数等将始终是一致的.

在实例化类之后,然后为您的多态性/重叠启动doSomething().您的父类与抽象类一样,定义了需要由子类匹配的参数和可见性,以支持覆盖.