我想创建一个带有抽象方法的抽象类,它允许在返回类型中使用抽象类型。在我的最后一个类中,我想覆盖返回的类型,该类型实现了最初声明的抽象类型。
<?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
In documentation, covariance is explained with interface.但不是抽象类。有关 PHP 实现的更多信息,文档说:
In PHP 7.2.0, partial contravariance was introduced by removing type restrictions on parameters in a child method. As of PHP 7.4.0, full covariance and contravariance support was added.
我使用的是 PHP 7.4。
最佳答案
面向对象编程的一个非常核心的原则是 Liskov substitution principle,它本质上可以归结为:
if
S
is a subtype ofT,
then objects of typeT
in a program may be replaced with objects of typeS
without altering any of the desirable properties of that program
实现这一点的方法是让协变方法返回类型,逆变方法类型参数。抛出的异常在这里算作返回类型,因此它们也需要是协变的。
您需要的是打破此原则的类型参数的协方差。 原因可以通过考虑下面的例子看出:
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
本身是 A
的 child ,而 C
通过简单的向下转换也是 A
的 child (这是允许的)限制被绕过。您始终可以禁用向下转换,但这几乎等于是在禁用继承,这是 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;
}
}
然而,这有点困惑而且可能是不必要的。
关于PHP 与继承类的协方差 - 声明不兼容,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61403800/