Можете ли вы переопределить методы интерфейса с разными, но "совместимыми" сигнатурами?
Рассмотрим следующие интерфейсы 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
extends Item
, поэтому я бы посмотрел add(SuperItem)
так же, как add(Item)
.
Мне любопытно, поддерживает ли это PHP (версия 5.4 или выше), и, возможно, IDE имеет ошибку, которая не улавливает это.
Ответы
Ответ 1
Нет, я уверен, что PHP не поддерживает это, в любой версии, и это скорее победит в точке интерфейса.
Точка интерфейса заключается в том, что он дает фиксированный контракт с другим кодом, который ссылается на один и тот же интерфейс.
Например, рассмотрим такую функцию:
function doSomething(Collection $loopMe) { ..... }
Эта функция ожидает получить объект, реализующий интерфейс Collection
.
Внутри функции программист сможет писать вызовы методам, определенным в Collection
, зная, что объект будет реализовывать эти методы.
Если у вас есть переопределенный интерфейс, подобный этому, тогда у вас есть проблема с этим, потому что объект SuperCollection
может быть передан в функцию. Он будет работать, потому что он также является объектом Collection
из-за наследования. Но тогда код в функции больше не мог быть уверен, что он знает, что такое определение метода add()
.
Интерфейс является, по определению, фиксированным контрактом. Он неизменен.
В качестве альтернативы вы можете рассмотреть использование абстрактных классов вместо интерфейсов. Это позволит вам переопределить в нестрочном режиме, хотя по тем же причинам вы по-прежнему будете получать ошибки, если используете Strict Mode.
Ответ 2
В качестве обходного пути я использую блоки 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
Вы не можете изменить аргументы методов.
http://php.net/manual/en/language.oop5.interfaces.php
Ответ 4
Проблема не в среде IDE. В PHP вы не можете переопределить метод. И совместимость только в обратном направлении - вы можете надежно ожидать экземпляр родительского класса и получить подкласс. Но когда вы ожидаете подкласс, вы не можете быть в безопасности, если получаете родительский класс - подкласс может определять методы, которых нет в родительском. Но все же вы не можете переопределить метод
Ответ 5
Когда у меня есть метод, который мне может потребоваться перегрузить (какой PHP не поддерживает), я убеждаюсь, что один из аргументов метода (обычно последний) является массивом. Таким образом я могу пройти все, что мне нужно. Затем я могу проверить внутри функции для различных элементов массива, чтобы рассказать мне, какую процедуру в методе мне нужно выполнить, обычно в select/case.
Ответ 6
Расширение интерфейса не позволяет изменять определения методов. Если ваш SuperItem расширяет Item, он должен без проблем проходить через классы, реализующие интерфейс Collection.
Но исходя из того, что вы действительно хотите сделать, вы можете попробовать:
-
Создайте интерфейс с немного другими методами для SuperItem и реализуйте это:
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);
}
}
Ответ 7
Ответ изменится позже в этом году (2019), если PHP 7.4 будет выпущен, как запланировано, с улучшенной дисперсией типов.
Код из вопроса останется недействительным:
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); // This will still be a compile error
// more methods here that "override" the Collection methods like "add()" does
}
Потому что интерфейс Collection
гарантирует, что все, что его реализует, может принимать любой объект типа Item
в качестве параметра add
.
Однако следующий код будет действителен в PHP 7.4:
interface Item {
// some methods here
}
interface SuperItem extends Item {
// some extra methods here, not defined in Item
}
interface Collection {
public function add(SuperItem $item);
// more methods here
}
interface SuperCollection extends Collection {
public function add(Item $item); // no problem
// more methods here that "override" the Collection methods like "add()" does
}
В этом случае Collection
гарантирует, что он может принять любой SuperItem
. Поскольку все SuperItem
являются Item
s, SuperCollection
также делает эту гарантию, а также гарантии того, что он может принимать любой другой тип Item
. Это известно как тип параметра метода Contravariant.
В современных версиях PHP существует ограниченная форма дисперсии типов. Предполагая, что другие интерфейсы соответствуют SuperCollection
, SuperCollection
может быть определен как:
interface SuperCollection extends Collection {
public function add($item); // no problem
// more methods here that "override" the Collection methods like "add()" does
}
Это можно интерпретировать как значение любого значения, которое может быть передано в метод add
. Это, конечно, включает в себя все Item
s, так что это все еще безопасный тип, или это может быть интерпретировано как означающее, что может быть передан неопределенный класс значений, обычно документированный как mixed
, и программист должен использовать другие знания о том, что именно может работать с функция.