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

2021-12-30 00:00:00 oop constructor php

这里有几个片段:

  1. 覆盖构造函数方法有一个额外的参数.

    class Cat {函数 __construct() {}}类狮子扩展猫{函数 __construct($param) {}}

  2. 覆盖(常规)方法有一个额外的参数.

    class Cat {函数 doSomething() {}}类狮子扩展猫{函数 doSomething($param) {}}

第一个可以工作,而第二个会抛出 Lion::doSomething() 的声明应该与 Cat::doSomething() 的声明兼容.

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

解决方案

要了解为什么它们会被区别对待,您必须了解 Liskov 的替换原则,陈述<块引用>

如果对于每个 S 类型的对象 o1 都有一个 T 类型的对象 o2 使得对于所有用 T 定义的程序 P,当 o1 替换 o2 时 P 的行为不变,那么 S 是 T 的子类型." - BarbaraLiskov,数据抽象和层次结构,SIGPLAN 通知,23,5(1988 年 5 月).

简而言之,这意味着任何使用您的 LionCat 的类都应该能够可靠地对其调用 doSomething,而不管该类是什么是其中之一.如果您更改方法签名,则不再有保证(您可以扩大它,但不能缩小它).

非常简单的例子

公共函数 doSomethingWithFeline(Cat $feline){$feline->doSomething(42);}

由于Lion 扩展了Cat,您建立了一个is-a 关系,这意味着doSomethingWithFeline 将接受Cat<的Lion/代码>.现在假设您向 Lion 中的 doSomething 添加一个必需的参数.上面的代码会中断,因为它没有传递那个新参数.因此,需要兼容的签名.

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

Here's a couple of snippets:

  1. Overriding constructor method has an extra parameter.

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

  2. Overriding (regular) method has an extra parameter.

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

The first would work, while the second would throw Declaration of Lion::doSomething() should be compatible with that of Cat::doSomething().

Why the special attitude towards constructor methods?

解决方案

To understand why they are treated differently, you have to understand Liskov's Substitution Principle, which states

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T." - BarbaraLiskov, Data Abstraction and Hierarchy, SIGPLAN Notices, 23,5 (May, 1988).

In a nutshell this means any class using your Lion or Cat should be able to reliably call doSomething on it, regardless of the class being one or the other. If you change the method signature, this is no longer guaranteed (you may widen it, but not narrow it though).

Very simple Example

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

Since Lion extends Cat, you established an is-a relationship, meaning doSomethingWithFeline will accept a Lion for a Cat. Now imagine you add a required argument to doSomething in Lion. The code above would break because it is not passing that new param. Hence, the need for compatible signatures.

LSP does not apply to constructors though, because subtypes might have different dependencies. For instance if you have a FileLogger and a DBLogger, the ctors (constructors) of the first would require a filename, while the latter would require a db adapter. As such, ctors are about concrete implementations and not part of the contract between classes.

相关文章