Liskov 替换原则和 PHP 接口

Posted

技术标签:

【中文标题】Liskov 替换原则和 PHP 接口【英文标题】:Liskov Substitution Principle and PHP Interfaces 【发布时间】:2019-10-29 18:29:26 【问题描述】:

以下代码是否直接违反了 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) 兼容

【问题讨论】:

当您在界面中专门要求 Baz 时,您正在尝试输入子类型。尽管 Foo 是 Baz 的子类型,但它仍然不是请求的类。 @MHewison 这不是重点吗?这应该是可能的,问题是 php 不支持逆变和/或协变。 是的,你说得对,PHP 不支持它。由于 PHP 中的方法参数类型是不变的。必须至少遵守接口。在 PHP 中,您通常会创建一个适合您的实现的接口。所以我想,这通常不违反 LSP,但在 PHP 中不支持。 @MHewison 谢谢。那么看来我对LSP还不是很了解。对我来说,看起来 Foo 作为 Baz 的子类型应该可以替代 Baz。 子类型必须可以替代它们的基本类型 - Robert C. Martin 一个实现类似于interface a class A implements a class B implements a 然后class Bar public function baz(a $implementation) 注意,接口代替父类型的位置。 【参考方案1】:

您的示例是非法的这一事实不会破坏 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 年底发布。

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

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

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

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

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

【讨论】:

这是 Laravel 新闻发布的一篇文章,解决了同样的问题。 linkedin.com/feed/update/urn:li:activity:6542838683446452224 如果作者没记错的话,这个 C# 实现完全没有问题。所以,我仍然对这个答案持怀疑态度。 看来这是知道的“问题”。 php.net/manual/en/language.oop5.interfaces.php#113793

以上是关于Liskov 替换原则和 PHP 接口的主要内容,如果未能解决你的问题,请参考以下文章

软件设计----LisKov替换原则(LSP)

Liskov替换原则

里氏替换原则(Liskov Substitution Principle)

下面的例子是不是违反了 Liskov 替换原则?

GWT 的 ActivityMapper 是不是违反了 Liskov 替换原则?

敏捷软件开发 – LSP Liskov替换原则