Объявление Specific::method(): должно быть совместимо с General::method(). PHP не прав насчет LSP?

Я полагаю, что "... должно быть совместимо с..." для обеспечения соблюдения принципа замены Лискова. Но я не уверен, что это то, что говорит 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: объявление Specific::create(): Specific должно быть совместимо с General::create(): General в...

LSP часто упоминается как:

Функции, которые используют указатели или ссылки на базовые классы, должны иметь возможность использовать объекты производных классов, не зная об этом.

И это не нарушено здесь, насколько я понимаю. Так что здесь не так? Это какое-то особое ограничение, которое не имеет ничего общего с LSP? Это ошибка в PHP? Я делаю что-то не так, не зная?

ОБНОВЛЕНИЕ: я нашел этот поток ( ковариация типа параметра в специализациях). Я понимаю и полностью согласен, что пример там нарушает LSP. Но у меня ситуация другая (на самом деле наоборот).

1 ответ

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

При переопределении родительского метода дочерний метод должен соответствовать любому объявлению возвращаемого типа в родительском методе. Если родитель не определяет тип возвращаемого значения, тогда дочерний метод может сделать это.

Ваш Specific::create функция должна указывать General тип возврата.

В противном случае код написан для Specific::create потенциально может сломаться при запуске General::createкак бы получил другой класс.

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 не выдает ошибок по этому поводу.

Это можно исправить, добавив PhpDoc в \General::create():

class General
{
    /**
     * @return static
     */
    public static function create(): General
    {
        return new static;
    }
Другие вопросы по тегам