Liskov 替换原理和 PHP 接口



下面的代码是否直接违反了Liskov替换原则:
子类永远不应该破坏父类的类型定义。

class Baz {}
class Foo extends Baz {}
interface a
{
public function baz(Baz $baz);
}
class b implements a
{
public function baz(Foo $foo)
{
}
}

结果是:

致命错误:b::baz(Foo $foo) 的声明必须与 a::baz(Baz $baz) 兼容

您的示例是非法的这一事实不会破坏 LSP。您的问题是正在定义一个接口,并且在实现它时应遵守该合同。

重要的是要记住,最终LSP处理的是对象,而不是

通过实现接口a,然后尝试使方法签名不兼容,类b的用户可能会尝试调用b::baz()并失败,因为a::baz()的签名需要Baz,而b::baz()上的不兼容实现需要实例Foo

例如,如果您的建议是合法的,则可能会发生这种情况:

$baz = new Baz();
$b = new b();
// since a::baz(Baz) is specified, the class user believes this
// should be possible, but your illegal implementation
// breaks that expectation
$b->baz($baz); // Not accepted!

这根本不会破坏 LSP。

正确实现接口的类a仍然可以接受类FooBaz的对象,因为可以使用子类型的对象而不是超类型的对象,正如 LSP 所说。

你不能做的是编写一个具有不兼容签名的方法,未来的协方差和逆变支持也不允许这样做。


关于返回类型和参数类型的协方差和逆变支持:PHP 7.4 中将提供支持,将于 2019 年底发布。

您可以在此处阅读(已接受)提案的详细信息。

  • 协方差将仅支持返回类型(因此,如果父类或接口声明的返回类型为T,则定义现在可以指定T的子类)

  • 参数类型
  • 将支持逆变(因此,如果父类或接口声明的参数类型为T,则子类或实现类现在可以将T的超类型声明为参数类型)

在确定方法与其父级方法的兼容性时,只要新类型仍接受父级指定的类型,引擎现在就应该允许不太具体的参数类型和更具体的返回类型。换句话说:参数类型可以替换其超类型之一,返回类型可以替换子类型。

同样,这将允许LSP支持,并允许类用户能够信任他们正在编程的抽象,而不必检查他们正在使用的具体类的细节。

最新更新