提问者:小点点

利斯科夫替换原则和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)兼容


共1个答案

匿名用户

你的例子是非法的,但这并不违反LSP。你的问题是定义一个接口,并期望在实现它的时候遵守契约。

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

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

E、 例如,如果你的提议是合法的,这可能会发生:

$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 的子类)

    参数类型将支持逆变(这样,如果父类或接口声明了< code>T的参数类型,子类或实现类现在可以将< code>T的超类型声明为参数类型)

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

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