Intereting Posts
Просмотр переменных сеанса php Чтобы удвоить побег или не удвоить бегство в функциях PHP PCRE? удалить все, кроме имени файла php simplexml_load_string () не любит иностранные языки Выделите поисковый запрос в mysql php search WooCommerce 2.6 – Скрытие оплачиваемой доставки, когда бесплатная доставка запускается путем достижения определенной суммы Защита API REST и Slim Framework извлекать тело из сырой электронной почты с регулярным выражением Проверка Laravel: требуется только одно поле и только одно поле Отправка файла вместе с данными формы через сообщение ajax Создать фиксированную длину, не повторяющуюся перестановку большего набора PHPMailer, сохранить 1 SMTP-соединение с другим получателем по контенту электронной почты Получение сохраненных имен и значений флажков из базы данных Общий обход дерева (бесконечный) в режиме поиска в ширину Ошибка CakePHP: невозможно настроить сеанс, не удалось установить session.auto_start

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

Для иллюстрации моего вопроса я буду использовать следующий пример:

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() s должна точно совпадать с сигнатурой его родительского абстрактного класса.

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

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

 SimpleAttribute instanceof Attribute // => true 

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

Solutions Collecting From Web of "Есть ли способ переопределить подсказку типа к классу потомков при расширении абстрактного класса?"

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

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

foo вызывает update и передает атрибут фабрике, который он должен принять, потому что подпись метода AbstractFactory::update обещает, что она может принимать атрибут. Бам! У 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 . Это приводит к фатальной ошибке «Call to undefined method», что означает, что LSP был нарушен.

Принятый ответ правилен в ответе на ОП, что то, что он пытался сделать, нарушает Принцип замещения Лискова. 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, что весь код, зависящий от фабрики для SimpleAttributes, будет кодироваться против 2) Внедрение SimpleFactory с использованием универсальной фабрики потребовало бы, чтобы SimpleFactory брал через конструктор экземпляр производного класса AbstractFactory, который он затем будет использоваться в функции обновления из переопределения метода интерфейса ISimpleFactory в SimpleFactory.

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

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