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

Posted

技术标签:

【中文标题】为啥 PHP 允许“不兼容”的构造函数?【英文标题】:Why does PHP allow "incompatible" constructors?为什么 PHP 允许“不兼容”的构造函数? 【发布时间】:2013-03-03 01:42:16 【问题描述】:

这里有几个sn-ps:

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

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

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

【问题讨论】:

【参考方案1】:

__construct(),因为它们在每个类中都是唯一的。 Lion 的构造函数与 Cat 的构造函数不同,尽管如果 Lion 扩展 CatLion 没有 __construct(),您仍然可以使用 Lion::__construct() 扩展父 __construct() .

与其他方法不同,php 不会生成 E_STRICT 级别 __construct() 被不同的覆盖时的错误消息 比父 __construct() 方法具有的参数。

PHP Manual: Constructors and Destructors

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

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

【讨论】:

【参考方案2】:

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

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

总之:

构造函数不使用重载,但仅用于类本身 该方法使用重载,因此不允许

【讨论】:

默认情况下也不会调用父非构造方法。【参考方案3】:

要了解他们为何受到不同对待,您必须了解Liskov's Substitution Principle,即states

如果对于每个 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将接受Lion作为Cat。现在假设您在Lion 中向doSomething 添加了一个必需的参数。上面的代码会中断,因为它没有传递那个新参数。因此,需要兼容的签名。

LSP does not apply to constructors,because subtypes might have different dependencies。例如,如果你有一个 FileLogger 和一个 DBLogger,第一个的 ctors(构造函数)需要一个文件名,而后者需要一个 db 适配器。因此,ctor 是关于具体实现的,而不是类之间契约的一部分。

【讨论】:

会不会永远这样,还是在2018年,构造函数可以有不同的签名,这对我正在做的事情有好处..【参考方案4】:

Liskov Substitution Principle 声明“如果 S 是 T 的子类型,则 T 类型的对象可以替换为 S 类型的对象”。在您的示例中,这意味着您应该能够将 Cat 类型的对象替换为 Lion 类型的对象。

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

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

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

【讨论】:

【参考方案5】:

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

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

否则你会创建一个有点 shizo 的对象。


编辑:如果您还想通过构造函数签名引入严格检查,您可以使用interface,因为 PHP 5.2 添加了构造函数检查:

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

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

【讨论】:

以上是关于为啥 PHP 允许“不兼容”的构造函数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥显式允许默认构造函数和具有 2 个或更多(非默认)参数的构造函数?

为啥构造函数被调用两次

为啥枚举的构造函数不能访问静态字段?

为啥枚举可以有包私有构造函数?

为啥 sql 数据库不允许为所有查询创建函数而不是在调用程序中构造 sql 字符串?

为啥 mxml 不支持组件构造函数?