PHP与继承类的共通性--声明不兼容。

问题描述 投票:2回答:1

我想创建一个具有抽象方法的抽象类,允许在返回类型中使用抽象类型。在我的最终类中,我想用实现最初声明的抽象类型的类型覆盖返回类型。

<?php

abstract class A {
    abstract public function test(A $foo): self;
}

class B extends A {
    public function test(B $foo): self
    {
        return $this;
    }
}

这个编译错误被抛出。

Fatal error: Declaration of B::test(B $foo): B must be compatible with A::test(A $foo): A in ... on line 8

在文档中,共变是用接口来解释的。 但不是抽象类。更多关于PHP的实现,文档中说。

在PHP 7.2.0中,通过取消子方法中参数的类型限制,引入了部分共变性。从 PHP 7.4.0 开始,增加了对完全协方差和协方差的支持。

我使用的是 PHP 7.4。

php covariance
1个回答
1
投票

面向对象程序设计的一个相当核心的原则是 "抽象"。利斯科夫替代原理 本质上归结为。

如果 ST, 那么 T 的对象来代替。S 而不改变该程序的任何理想特性。

实现这一目标的方法是拥有 共变 方法返回类型。违变 方法类型的参数。抛出的异常在这里算是返回类型,所以它们也需要是共变的。

你需要的是 协方差 的类型参数,打破了这一原则。原因可以从下面的例子中看出。

abstract class A {
    abstract public function test(A $foo): self;
}

class C extends A {
    public function test(C $foo): self {
        return $this;
    }
}

class B extends A {
    public function test(B $foo): self {
        return $this;
    }
}

$b = new B();
$c = new C();

$b->test($c); // Does not work
((A)$b)->test((A)$c); // Works


在上面的例子中,你不允许使用 B::test 以外的任何类型,接受 B 作为类型参数。然而由于 B 本身就是一个孩子 AC 也是一个孩子 A 通过简单的向下转述(这是允许的),这个限制被绕过了。你总是可以禁止向下转换,但这几乎是说你在禁止继承,而继承是OOP的一个核心原则。

现在当然有令人信服的理由来允许类型参数的共变,这就是为什么一些语言(如 Eiffel)允许的,但这是一个公认的问题,甚至被命名为 CATcalling (CAT代表改变的可用性或类型)。

在PHP中,你可以尝试做运行时检查来补救这种情况。

abstract class A {
    abstract public function test(A $foo) {
         // static keyword resolve to the current object type at runtime 
         if (!$foo instanceof static) { throw new Exception(); }  
    }
}

class C extends A {
    public function test(A $foo): self {
        parent::test($foo);
        return $this;
    }
}

class B extends A {
    public function test(A $foo): self {
        parent::test($foo);
        return $this;
    }
}

然而这有点混乱,而且可能没有必要。

© www.soinside.com 2019 - 2024. All rights reserved.