28

这里有几个片段:

  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().

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

4

5 回答 5

36

要了解为什么它们被区别对待,您必须了解Liskov 的替代原则,它指出

如果对于每个 S 类型的对象 o1 都有一个 T 类型的对象 o2 ,那么对于以 T 定义的所有程序 P ,当 o1 替换 o2 时 P 的行为不变,则 S 是 T 的子类型。” - BarbaraLiskov,Data Abstraction and Hierarchy,SIGPLAN Notices,23,5(1988 年 5 月)。

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

非常简单的例子

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

既然Lion extends Cat,你建立了一个 is-a 关系,意思就是doSomethingWithFeline接受Liona Cat。现在假设您向doSomethingin添加了一个必需的参数Lion。上面的代码会中断,因为它没有传递那个新参数。因此,需要兼容的签名。

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

于 2013-03-16T11:18:49.677 回答
12

Liskov 替换原则指出“如果 S 是 T 的子类型,则 T 类型的对象可以被 S 类型的对象替换” 。在您的示例中,这意味着您应该能够将 type 的对象替换为 typeCat的对象Lion

这就是不允许您的第二个代码的原因。您将不再能够进行此替换,因为您将不再能够在->doSomething()没有参数的情况下调用该方法。

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

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

于 2013-03-16T11:21:03.560 回答
5

因为__construct()它们在每个班级都是独一无二的。的构造函数Lion与 的构造函数不同Cat,尽管如果LionextendsCat并且Lion没有__construct(),您仍然可以__construct()使用扩展父级Lion::__construct()

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

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

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

在类被实例化后,多态性/覆盖会为你的doSomething(). 您的父类,就像抽象类所做的那样,定义了需要由子类匹配以支持覆盖的参数和可见性。

于 2013-03-14T15:52:03.903 回答
2

构造函数仅适用于具体对象。它孕育了它。

其他方法 - 只要涉及子类型 - 与继承的类相关,因此这些方法是同一对象的一部分,因此当分布在多个类的定义中时应该是兼容的。

否则,您将创建一个有点 shizo 的对象。


编辑:如果您还想使用构造函数签名引入严格检查,您可以使用interfacePHP 5.2 以来添加了构造函数检查:

在接口中添加了对构造函数的支持,以强制在实现中进行构造函数签名检查。从 PHP 5.2.0 开始,接口可以有构造函数。但是,如果您选择在接口中声明构造函数,则实现该接口的每个类都必须包含一个构造函数,该构造函数的签名与基接口构造函数的签名匹配。“签名”是指参数和返回类型定义,包括任何类型提示,包括数据是通过引用传递还是通过值传递。

(取自:Other Enhancements - Migrating from PHP 5.1.x to PHP 5.2.x

于 2013-03-16T11:38:44.160 回答
1

您所描述的是PHP 不支持的重载。现在,当您为类创建构造函数时,它仅在该类中使用,并且默认情况下不调用父构造函数(请参阅构造函数和析构函数。您需要手动调用它parent::__construct()

当您在类中创建方法时,它会直接访问父方法。

所以总而言之:

  • 构造函数不使用重载,但仅适用于类本身
  • 该方法使用重载,因此不允许
于 2013-03-14T15:56:21.797 回答