Можете ли вы переопределить методы интерфейса с помощью разных, но "совместимых" сигнатур?


Рассмотрим следующие интерфейсы PHP:

interface Item {
    // some methods here
}

interface SuperItem extends Item {
    // some extra methods here, not defined in Item
}

interface Collection {
    public function add(Item $item);
    // more methods here
}

interface SuperCollection extends Collection {
    public function add(SuperItem $item);
    // more methods here that "override" the Collection methods like "add()" does
}

Я использую PhpStorm, и когда я это делаю, я получаю ошибку в IDE, в которой в основном говорится, что определение add() в SuperCollection несовместимо с определением в интерфейсе, который он расширяет, Collection.

С одной стороны, я вижу, что это проблема, так как сигнатура метода не совпадает с той, которую он "переопределяет" точно. Тем не менее, я чувствую, что это было бы совместимо, поскольку SuperItem расширяет Item, поэтому я будет просматривать add(SuperItem) так же, как add(Item).

Мне любопытно, поддерживается ли это PHP (версия 5.4 или выше), и, возможно, в IDE есть ошибка, которая неправильно улавливает это.

Author: hakre, 2013-02-22

6 answers

Нет, я почти уверен, что PHP не поддерживает это ни в одной версии, и это скорее разрушит суть интерфейса.

Суть интерфейса в том, что он дает вам фиксированный контракт с другим кодом, который ссылается на тот же интерфейс.

Например, рассмотрим такую функцию:

function doSomething(Collection $loopMe) { ..... }

Эта функция ожидает получения объекта, реализующего интерфейс Collection.

Внутри функции программист мог бы писать вызовы методов которые определены в Collection, зная, что объект будет реализовывать эти методы.

Если у вас есть переопределенный интерфейс, подобный этому, то у вас есть проблема с этим, потому что объект SuperCollection может быть передан в функцию. Это сработало бы, потому что это также объект Collection из-за наследования. Но тогда код в функции больше не мог быть уверен, что он знает, каково определение метода add().

Интерфейс по определению является фиксированным контрактом. Это неизменяемый.

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

 12
Author: Spudley, 2013-02-21 20:44:26

В качестве обходного пути я использую блоки PHPDoc в интерфейсах.

interface Collection {
   /**
    * @param Item $item
    */
    public function add($item);
    // more methods here
}

interface SuperCollection extends Collection {
    /**
    * @param SuperItem $item
    */
    public function add($item);
    // more methods here that "override" the Collection methods like "add()" does
}

Таким образом, если вы правильно используете интерфейсы, IDE должна помочь вам отловить некоторые ошибки. Вы также можете использовать аналогичную технику для переопределения типов возвращаемых значений.

 3
Author: Nik Denisov, 2017-05-23 12:32:24

Вы не можете изменить аргументы методов.

Http://php.net/manual/en/language.oop5.interfaces.php

 2
Author: Galen, 2013-02-21 20:38:40

Проблема не в IDE. В PHP вы не можете переопределить метод. И совместимость только в противоположном направлении - вы можете смело ожидать экземпляр родительского класса и получать подкласс. Но когда вы ожидаете подкласс, вы не можете быть в безопасности, если получите родительский класс - подкласс может определять методы, которые не существуют в родительском классе. Но все же вы не можете переопределить метод

 1
Author: Maxim Krizhanovsky, 2013-02-21 20:38:51

Когда у меня есть метод, который мне может потребоваться перегрузить (который PHP не поддерживает), я удостоверяюсь, что один из аргументов метода (обычно последний) является массивом. Таким образом, я могу передать все, что мне нужно. Затем я могу протестировать в функции различные элементы массива, чтобы узнать, какую процедуру в методе мне нужно выполнить, обычно в случае выбора/.

 0
Author: Craig Jacobs, 2015-12-11 22:11:58

Расширение интерфейса не позволяет изменять определения методов. Если ваш суперэлемент расширяет элемент, он должен без проблем проходить через классы, реализующие интерфейс коллекции.

Но, исходя из того, что вы действительно хотите сделать, вы можете попробовать:

  • Создайте интерфейс с немного другими методами для суперэлемента и реализуйте это:

    interface SuperCollection extends Collection {
        public function addSuper(SuperItem $superItem);
    }
    
  • Используйте шаблон декоратора для создания почти такого же интерфейса без расширение:

    interface Collection {
        public function add(Item $item);
        // more methods here
    }
    
    interface SuperCollection {
        public function add(SuperItem $item);
        // more methods here that "override" the Collection methods like "add()" does
    }
    

    Затем класс декоратора (абстрактный), который будет использовать этот интерфейс:

    class BasicCollection implements Collection {
        public function add(Item $item)
        {
        }
    }
    
    class DecoratingBasicCollection implements SuperCollection {
        protected $collection;
    
        public function __construct(Collection $collection)
        {
            $this->collection = $collection;
        }
    
        public function add(SuperItem $item)
        {
            $this->collection->add($item);
        }
    }
    
 0
Author: Peter Laboš, 2018-03-06 10:55:26