Подтвердить что ты не робот

Есть ли способ переопределить подсказку типа к классу потомков при расширении абстрактного класса?

Я буду использовать следующий пример, чтобы проиллюстрировать мой вопрос:

class Attribute {}

class SimpleAttribute extends Attribute {}



abstract class AbstractFactory {
    abstract public function update(Attribute $attr, $data);
}

class SimpleFactory extends AbstractFactory {
   public function update(SimpleAttribute $attr, $data);

}

Если вы попытаетесь запустить это, PHP будет генерировать фатальную ошибку, заявив, что Declaration of SimpleFactory::update() must be compatible with that of AbstractFactory::update()

Я точно понимаю, что это значит: эта сигнатура метода SimpleFactory::update() должна точно соответствовать таковой родительского абстрактного класса.

Однако мой вопрос: есть ли способ разрешить конкретный метод (в данном случае, SimpleFactory::update()) переопределить подсказку типа действительному потомку исходного подсказки?

Примером может служить оператор instanceof, который вернет true в следующем случае:

SimpleAttribute instanceof Attribute // => true

Я понимаю, что, работая вокруг, я мог бы сделать тип hint одним и тем же в конкретном методе и сделать экземпляр проверки в самом тесте метода, но есть ли способ просто обеспечить его соблюдение на уровне подписи?

4b9b3361

Ответ 1

Я бы так не ожидал, так как он может разорвать контракты типа "намек". Предположим, что функция foo взяла AbstractFactory и была передана SimpleFactory.

function foo(AbstractFactory $maker) {
    $attr = new Attribute();
    $maker->update($attr, 42);
}
...
$packager=new SimpleFactory();
foo($packager);

foo вызывает update и передает атрибут factory, который он должен принять, поскольку подпись метода AbstractFactory::update promises может принимать атрибут. Бам! У SimpleFactory есть объект типа, который он не может обработать должным образом.

class Attribute {}
class SimpleAttribute extends Attribute {
    public function spin() {...}
}
class SimpleFactory extends AbstractFactory {
    public function update(SimpleAttribute $attr, $data) {
        $attr->spin(); // This will fail when called from foo()
    }
}

В терминарной терминологии классы потомков должны соблюдать контракты своих предков, что означает, что параметры функции могут получить больше базальных/менее указанных/предлагать более слабый контракт, а возвращаемые значения могут быть более производными/более конкретными/предлагать более сильный контракт. Принцип описан для Eiffel (возможно, самый популярный язык дизайна по контракту) в "" Эйфелева учебник: наследование и контракты ". Ослабление и усиление типов являются примерами контравариантности и ковариации соответственно.

В более теоретических терминах это пример нарушения LSP. Нет, не то, что LSP; Принцип замены Лискова, в котором говорится, что объекты подтипа могут быть заменены объектами супертипа. SimpleFactory является подтипом AbstractFactory, а foo принимает AbstractFactory. Таким образом, согласно LSP, foo должен взять SimpleFactory. Это приводит к фатальной ошибке "Вызов метода undefined", что означает, что LSP был нарушен.

Ответ 2

Принятый ответ правилен, отвечая на ОП, что то, что он пытался сделать, нарушает Принцип замещения Лискова. OP должен использовать новый интерфейс и использовать композицию вместо наследования для решения своей проблемы с подписями функций. Изменение в примере примера OP довольно незначительное.

class Attribute {}

class SimpleAttribute extends Attribute {}

abstract class AbstractFactory {
    abstract public function update(Attribute $attr, $data);
}

interface ISimpleFactory {
    function update(SimpleAttribute $attr, $data);
}

class SimpleFactory implements ISimpleFactory {
   private $factory;
   public function __construct(AbstractFactory $factory)
   {
       $this->factory = $factory;
   }
   public function update(SimpleAttribute $attr, $data)
   {
       $this->factory->update($attr, $data);
   }

}

Пример кода, приведенный выше, делает две вещи: 1) Создает интерфейс ISimpleFactory, что весь код, зависящий от factory для SimpleAttributes, будет кодировать против 2) Внедрение SimpleFactory с использованием общего factory потребует, чтобы SimpleFactory выполнял через конструктор an экземпляр производного класса AbstractFactory, который затем будет использоваться в функции обновления из переопределения метода интерфейса ISimpleFactory в SimpleFactory.

Это позволяет любой зависимости от общего factory быть инкапсулированным из любого кода, который зависит от ISimpleFactory, но разрешает SimpleFactory заменять любой factory, полученный из AbstractFactory (удовлетворительный LSP), без необходимости изменять какой-либо из его кода ( вызывающий код обеспечит зависимость). Новый производный класс ISimpleFactory может решить не использовать какие-либо экземпляры, созданные для AbstractFactory, для реализации самого себя, и весь код вызова будет защищен от этой детали.

Наследование может иметь большое значение, однако иногда композиция упускается из виду и ее предпочтительный способ уменьшить жесткую связь.