【问题标题】:Declaration of Specific::method(): must be compatible with General::method(). Is PHP wrong about LSP?具体::method() 的声明:必须与 General::method() 兼容。 PHP 对 LSP 有误吗?
【发布时间】:2018-09-26 08:17:32
【问题描述】:

我猜想“...必须与...兼容”是为了执行 Liskov 替换原则。但我不确定这是 LSP 所说的吗?

我有这样的代码:

class General
{
    public static function create(): General
    {
        return new static;
    }

    public function doSomething()
    {
        echo get_class($this) . ' speaking!' . PHP_EOL;
    }
}


class Specific extends General
{
    public static function create(): Specific
    {
        return parent::create();
    }
}


function doSomething(General $object)
{
    $object->doSomething();
}

doSomething(General::create());
doSomething(Specific::create());

产生:

PHP 致命错误:Declaration of Specific::create(): Specific must be compatible with General::create(): General in ...

LSP 经常被引用为:

使用基类指针或引用的函数必须能够 在不知情的情况下使用派生类的对象。

据我所知,这里没有违反这一点。那么这里有什么问题呢?是不是一些与 LSP 无关的特殊限制?它是PHP中的错误吗?我是不是在不知不觉中做错了什么?

更新:我找到了这个帖子 (Parameter type covariance in specializations)。我理解并完全同意该示例违反了 LSP。但我的情况不一样(其实相反)。

【问题讨论】:

  • 我相信这是您的返回类型声明。依赖于Specific::create 返回Specific 的代码如果在调用General::create 时得到General 类,则会中断。
  • 好的,但是为特定类编写的代码无法获取通用类,因为 typehint 会阻止它通过。具体扩展一般,而不是相反。例如。 doSomethingDifferent(Specific $object) 永远无法将 General 类作为 $object。
  • 我认为您在使用接口时遇到了同样的问题。您期待协变行为,但 PHP 仅支持不变行为。

标签: php liskov-substitution-principle


【解决方案1】:

http://php.net/manual/en/functions.returning-values.php

当覆盖父方法时,子方法必须匹配父方法的任何返回类型声明。如果父方法没有定义返回类型,那么子方法可能会这样做。

您的Specific::create 函数应指示General 返回类型。

否则,为Specific::create 编写的代码在运行General::create 时可能会中断,因为它会接收不同的类。

【讨论】:

  • 谢谢!你能详细说明你的最后一句话吗?我无法完全想象这种情况。
  • 所以 LSP 说“使用基类指针或引用的函数必须能够在不知情的情况下使用派生类的对象。”如果您的派生类返回一些根本不同的东西(即使在您的情况下,它功能上没有不同 - ),基类不一定依赖于该派生类的返回值。
  • 我明白了。但我的观点是,在这种情况下,情况并非如此。因为doSomething函数(引用基类General的函数)总是能够使用派生自General的派生类(Specific)的对象。这意味着 doSomething 获得了它期望的类(General)。具体是一般的,也是具体的。你知道我的意思?我有点迷失了解释:-)
【解决方案2】:

LSP 规定方法参数必须是逆变的,而返回值必须是协变的。

在您的情况下,您有满足 LSP 的协变返回类型。

问题出在 PHP 本身。这个限制与LSP无关,只是PHP的早期版本还没有实现。

从 PHP 7.2 (7.4) 开始,它现在完全支持 LSP,即参数逆变和返回值协方差:https://www.php.net/manual/en/language.oop5.variance.php

UPD. 但是您的代码包含另一个问题:您的 \Specific::create() 方法必须返回 Specific 的实例,正如您在其签名中所述,但它试图返回由\General::create() 被称为General(我们知道General 的实例不是instanceof Specific)。这是相当误导的。例如。 PhpStorm 会警告你:Return value is expected to be 'Specific', 'General' returned。 但是 PHP 并没有对此抛出错误。

这可以通过向\General::create() 添加一个 PhpDoc 来解决:

class General
{
    /**
     * @return static
     */
    public static function create(): General
    {
        return new static;
    }

【讨论】:

    猜你喜欢
    • 2012-07-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-04
    • 2011-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-11
    相关资源
    最近更新 更多