【问题标题】:Why is overriding method parameters a violation of strict standards in PHP?为什么覆盖方法参数违反了 PHP 中的严格标准?
【发布时间】:2012-11-05 14:03:47
【问题描述】:

我知道 StackOverflow 中有几个类似的问题,例如 this question

为什么重写方法参数违反了 PHP 中的严格标准? 例如:

class Foo
{
    public function bar(Array $bar){}
}

class Baz extends Foo
{
    public function bar($bar) {}
}

严格标准:Baz::bar() 的声明应与 Foo::bar()

您可以使用其他 OOP 编程语言。为什么 PHP 不好?

【问题讨论】:

    标签: php oop


    【解决方案1】:

    您可以覆盖参数,但签名应该匹配。如果你把Array放在$bar前面,就没有问题了。

    例如,如果您添加了一个附加参数,那么只要第一个参数具有相同的类型提示,就没有问题。这是任何语言的好习惯。

    【讨论】:

    • 但是签名为什么要匹配呢?在 Java 中,我可以更改签名
    • @KaernStone,您也可以在 PHP 中使用,但您不应该这样做。假设您有一个名为BazFoo 的子类,并且一些其他代码想要与Baz 的一个实例进行交互,就好像它是一个Foo。它应该无需修改就可以做到这一点,因为Baz Foo。 PHP 将允许你打破这个原则,但你不应该这样做,除非你有很好的理由这样做。
    • @Brad,请在 Tivie 的回答中查看我的 cmets。如果没有在参数中放入类型意味着该方法可以采用任何对象,则意味着它们是逆变的,因此是 Baz is-a Foo。从打字系统的角度来看,应该没有问题。请参阅here
    • @AndrésFortier,这是一个很好的观点,我明白你在这个特定的例子中得到了什么。
    • “例如,如果您添加了一个附加参数,那么只要第一个参数具有相同的类型提示,就没有问题。” 这是错误的。如果你在Baz/child 类public function bar(Array $bar, $baz) {} 中声明它会抛出兼容性错误。
    【解决方案2】:

    因为您在Foo 上声明了$bar 的类型应该是array,而在扩展Bar 中,$bar 的类型没有被声明。

    这不是错误,而是警告。您应该使方法定义与原始基类兼容。但是,如果您知道自己在做什么,则可以放心地忽略它(并且只有如果您知道自己在做什么!!!)

    【讨论】:

    • 是的,我知道。但是为什么要让方法定义与原始的基类兼容呢?
    • @KaernStone:因为其他类/方法/应用程序依赖于那个原始接口,如果你改变它,你可能会破坏兼容性。
    【解决方案3】:

    在 OOP 中,SOLID 代表单一职责、开闭、Liskov 替换、接口隔离和依赖倒置

    Liskov substitution 原则指出,在计算机程序中,如果 BarFoo 的子类型,则 Foo 类型的对象可能用 Bar 类型的对象替换,而不改变该程序的任何所需属性(正确性、执行的任务等)。

    在强类型编程语言中,当重写 Foo 方法时,如果您更改 Bar 中的签名,您实际上是在重载,因为原始方法和新方法具有不同的签名。由于 PHP 是弱类型的,这是不可能实现的,因为编译器无法知道您实际调用了哪些方法。 (因此,即使它们的签名不同,您也不能有 2 个具有相同名称的方法)。

    因此,为了避免违反里氏替换原则,发出严格标准的警告,告诉程序员由于子类中方法签名的更改,可能会出现某些问题。

    【讨论】:

    • 我不同意这里的两点:“如果 Bar 是 Foo 的子类型,那么 Foo 类型的对象可能会被 Bar 类型的对象替换(反之亦然)”。反之部分不成立;您不希望在使用子类的任何地方使用超类。另一件事是关于 LSP:如果参数是逆变的,那么类型就会受到尊重。如果 PHP 是纯 OO 则不会有问题,因为 Array 会比 Object 更窄(假设声明中没有类型意味着任何对象)。因此 bar() 的实现在 Baz 中会更通用,使其适合 LSP
    • 很好的解释,谢谢。那为什么我可以在构造函数中打破里氏原理呢?
    • @Tivie:请不要把这个当作个人的,但我认为这样的答案具有误导性。我会投反对票。
    • @KaernStone LSP 不适用于 Ctor,因为这会限制多态性。不同的子类型可能需要不同的依赖关系。想想Log 和子类型DbLoggerFileLogger
    • @AndrésFortier “因此 bar() 的实现在 Baz 中将更加通用,使其适合 LSP”。子类只能更具体,不能更一般。此外,您的其他声明“LSP 应该始终成立,构造函数不应该是特例。”可以解释。构造函数不必遵守 LSP 的原因是新对象的创建永远不可互换。即使你把它放在工厂方法中,你要么创建A,要么最终创建B。对象创建中没有多态性。
    【解决方案4】:

    我知道我迟到了,但答案并没有真正说明实际问题。

    问题是 PHP 不支持函数/方法重载。非类型化语言很难支持函数重载。

    提示很有帮助。但在 PHP 中它非常有限。不知道为什么。例如,您不能暗示变量是 int 或 Boolean,但数组很好。算了!

    其他面向对象的语言使用函数重载来实现这一点。也就是说函数的签名明显不同。

    例如,如果以下情况是可能的,我们就不会有问题

    class Foo
    {
        public function bar(Array $bar){
            echo "Foo::bar";
        }
    }
    
    class Baz extends Foo
    {
        public function bar(int $bar) {
            echo "Baz::bar";
        }
    }
    
    
    $foo = new Baz();
    $bar = new Baz();
    $ar = array();
    $i = 100;
    
    $foo->bar($ar);
    $bar->bar((int)$i);
    
    would output
    
    Foo::bar
    Baz::bar
    

    当然,当涉及到构造函数时,php 开发人员意识到他们必须实现它,不管你喜不喜欢!所以他们只是在第一种情况下抑制错误或不引发错误。

    这很愚蠢。

    一位熟人曾经说过 PHP 实现对象只是作为实现命名空间的一种方式。现在我并不那么挑剔,但做出的一些决定确实倾向于支持该理论。

    在开发代码时,我总是打开最大警告,我从不让警告在不了解它的含义和含义的情况下消失。就我个人而言,我不关心这个警告。我知道我想做什么,而 PHP 做的不对。我来这里是为了寻找一种选择性抑制它的方法。我还没有找到方法。

    所以我会捕获这个警告并自己压制它。可惜我需要这样做。但我对 STRICT 很严格。

    【讨论】:

    • 关于构造函数,在 PHP 5.4+ 中,扩展 abstract 基类时方法签名必须兼容,否则会出现致命错误。有点奇怪,在 PHP 5.4 之前甚至没有 E_STRICT 警告,如果基类不是抽象的,那么无论 PHP 版本如何,都没有消息。
    • 谢谢谢谢谢谢。这是我第一次也是唯一一次看到 PHP 故意抑制构造函数中的错误,这是一个非常讨厌的问题。这违反了 LSP,因为在构造函数中允许子类型参数是加强先决条件的情况。方法参数应该永远是不变的或逆变的,而不是协变的。事实上,在 PHP 中,我相信这是唯一一种情况,除了重写方法中类型参数的不变性之外,其他任何事情都是可能的。
    猜你喜欢
    • 1970-01-01
    • 2017-05-28
    • 2019-08-11
    • 2014-12-10
    • 2013-01-08
    • 2017-01-25
    • 2016-04-17
    • 2014-11-03
    • 2013-03-31
    相关资源
    最近更新 更多