Какой смысл интерфейсов в PHP? - PullRequest
212 голосов
/ 21 августа 2008

Интерфейсы позволяют создавать код, который определяет методы классов, которые его реализуют. Однако вы не можете добавить код к этим методам.

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

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

Мне сказали, что это связано с теорией ОО от C ++ до Java, на которой основаны ООП в PHP. Является ли концепция полезной в Java, но не в PHP? Это просто способ избежать засорения заполнителей в абстрактном классе? Я что-то упустил?

Ответы [ 15 ]

137 голосов
/ 21 августа 2008

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

Интерфейсы - это компромисс. Большинство проблем с множественным наследованием неприменимы к абстрактным базовым классам, поэтому большинство современных языков в настоящее время отключают множественное наследование, но при этом вызывают интерфейсы абстрактных базовых классов и позволяют классу «реализовывать» столько их, сколько они хотят.

121 голосов
/ 21 августа 2008

Эта концепция полезна для объектно-ориентированного программирования. Для меня я думаю об интерфейсе как о контракте. До тех пор, пока мой класс и ваш класс договариваются об этом контракте подписи метода, мы можем «взаимодействовать». Что касается абстрактных классов, то я считаю их более базовыми классами, которые ограничивают некоторые методы, и мне нужно заполнить детали.

63 голосов
/ 26 июня 2014

Зачем вам нужен интерфейс, если уже есть абстрактные классы? Для предотвращения множественного наследования (может вызвать множество известных проблем).

Одна из таких проблем:

«Проблема алмазов» (иногда называемая «смертельным алмазом смерть ") - это двусмысленность, возникающая, когда два класса B и C наследуют от A и класса D наследует от B и C. Если есть метод в A, что B и C переопределяют, а D не переопределяет, то какую версию метода наследует D: версию B или версию C?

Источник: https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem

Почему / Когда использовать интерфейс? Пример ... Все машины в мире имеют одинаковый интерфейс (методы) ... AccelerationPedalIsOnTheRight(), BrakePedalISOnTheLeft(). Представьте, что у каждой автомобильной марки эти "методы" будут отличаться от другой марки. У БМВ были бы тормоза на правой стороне, и у Хонды были бы тормоза на левой стороне колеса. Люди должны были бы узнать, как работают эти «методы» каждый раз, когда покупали автомобиль другой марки. Вот почему хорошая идея иметь один и тот же интерфейс в нескольких «местах».

Что интерфейс делает для вас (почему кто-то даже использовал бы его)? Интерфейс не позволяет вам делать «ошибки» (он гарантирует, что все классы, которые реализуют определенный интерфейс, будут иметь методы, которые находятся в интерфейсе).

// Methods inside this interface must be implemented in all classes which implement this interface.
interface IPersonService
{   
    public function Create($personObject);
}

class MySqlPerson implements IPersonService
{
    public function Create($personObject)
    {
        // Create a new person in MySql database.
    }
}

class MongoPerson implements IPersonService
{
    public function Create($personObject)
    {
        // Mongo database creates a new person differently then MySQL does. But the code outside of this method doesn't care how a person will be added to the database, all it has to know is that the method Create() has 1 parameter (the person object).
    }
}

Таким образом, метод Create() всегда будет использоваться одинаково. Не имеет значения, используем ли мы класс MySqlPerson или класс MongoPerson. То, как мы используем метод, остается прежним (интерфейс остается тем же).

Например, он будет использоваться следующим образом (везде в нашем коде):

new MySqlPerson()->Create($personObject);
new MongoPerson()->Create($personObject);

Таким образом, что-то подобное не может произойти:

new MySqlPerson()->Create($personObject)
new MongoPerson()->Create($personsName, $personsAge);

Гораздо проще запомнить один интерфейс и использовать его везде, чем несколько.

Таким образом, внутренняя часть метода Create() может отличаться для разных классов, не затрагивая «внешний» код, который вызывает этот метод. Внешний код должен знать только то, что метод Create() имеет 1 параметр ($personObject), потому что именно так внешний код будет использовать / вызывать метод. Внешний код не заботится о том, что происходит внутри метода; ему нужно только знать, как его использовать / назвать.

Вы можете сделать это и без интерфейса, но если вы используете интерфейс, он "безопаснее" (потому что он предотвращает ошибки). Интерфейс гарантирует, что метод Create() будет иметь одинаковую сигнатуру (одинаковые типы и одинаковое количество параметров) во всех классах, которые реализуют интерфейс. Таким образом, вы можете быть уверены, что ЛЮБОЙ класс, который реализует интерфейс IPersonService, будет иметь метод Create() (в этом примере) и будет нуждаться только в 1 параметре ($personObject) для вызова / использования.

Класс, реализующий интерфейс, должен реализовывать все методы, которые интерфейс имеет / имеет.

Надеюсь, я не особо повторялся.

23 голосов
/ 27 августа 2008

Разница между использованием интерфейса и абстрактного класса больше связана с организацией кода для меня, чем с применением самого языка. Я часто использую их при подготовке кода для работы с другими разработчиками, чтобы они не выходили за рамки предполагаемых шаблонов проектирования. Интерфейсы - это своего рода «проектирование по контракту», когда ваш код соглашается отвечать на предписанный набор вызовов API, которые могут исходить из кода, к которому у вас нет доступа.

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

Например, допустим, у вас есть абстрактный класс Account, из которого берут начало многие другие классы (типы учетных записей и т. Д.). У него есть определенный набор методов, которые применимы только к этой группе типов. Однако некоторые из этих подклассов учетных записей реализуют Versionable, Listable или Editable, чтобы их можно было бросать в контроллеры, которые ожидают использования этих API. Контроллеру все равно, какой это тип объекта

В отличие от этого, я также могу создать объект, который не расширяется от Account, скажем, абстрактный класс User, и все еще реализует Listable и Editable, но не Versionable, что здесь не имеет смысла.

Таким образом, я говорю, что подкласс FooUser НЕ является учетной записью, но действует как редактируемый объект. Аналогично, BarAccount расширяется от Account, но не является подклассом User, но реализует Editable, Listable и также Versionable.

Добавление всех этих API-интерфейсов для Editable, Listable и Versionable в сами абстрактные классы не только будет беспорядочным и уродливым, но либо приведет к дублированию общих интерфейсов в Account и User, либо вынудит мой объект User реализовать Versionable, вероятно, просто бросить исключение.

20 голосов
/ 21 августа 2008

Интерфейсы - это, по сути, план того, что вы можете создать. Они определяют, какие методы класс должен иметь , но вы можете создавать дополнительные методы вне этих ограничений.

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

Метод в интерфейсе, применяемый к абстрактному классу, должен быть реализован в этом абстрактном классе. Однако примените этот интерфейс к расширяющему классу, и метод должен реализовываться только в расширяющем классе. Я могу ошибаться - я не использую интерфейсы так часто, как мог бы / должен.

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

11 голосов
/ 08 февраля 2013

Вы будете использовать интерфейсы в PHP:

  1. Чтобы скрыть реализацию - установите протокол доступа к классу объектов, измените базовую реализацию без рефакторинга во всех местах, где вы использовали эти объекты
  2. Для проверки типа - например, чтобы убедиться, что параметр имеет определенный тип $object instanceof MyInterface
  3. Для принудительной проверки параметров во время выполнения
  4. Для реализации нескольких поведений в одном классе (создание сложных типов)

    Класс Car реализует EngineInterface, BodyInterface, SteeringInterface {

так что Car объект может теперь start(), stop() (EngineInterface) или goRight(), goLeft() (интерфейс управления)

и другие вещи, о которых я не могу думать сейчас

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

Из мышления на Java:

Интерфейс говорит: «Вот как будут выглядеть все классы, реализующие этот конкретный интерфейс». Таким образом, любой код, использующий конкретный интерфейс, знает, какие методы могут быть вызваны для этого интерфейса, и это все. Таким образом, интерфейс используется для установления «протокола» между классами.

9 голосов
/ 30 июля 2015

Интерфейсы существуют не как база, на которую могут расширяться классы, а как карта требуемых функций.

Ниже приведен пример использования интерфейса, в котором не подходит абстрактный класс:
Допустим, у меня есть приложение календаря, которое позволяет пользователям импортировать данные календаря из внешних источников. Я написал бы классы для обработки импорта каждого типа источника данных (ical, rss, atom, json). Каждый из этих классов реализовывал бы общий интерфейс, который гарантировал бы, что все они имеют общие публичные методы, необходимые моему приложению для получения данных.

<?php

interface ImportableFeed 
{
    public function getEvents();
}

Затем, когда пользователь добавляет новый фид, я могу определить тип фида и использовать класс, разработанный для этого типа, для импорта данных. Каждый класс, написанный для импорта данных для определенного фида, будет иметь совершенно другой код, в противном случае между классами может быть очень мало общего, за исключением того факта, что они необходимы для реализации интерфейса, который позволяет моему приложению использовать их. Если бы я использовал абстрактный класс, я мог бы очень легко проигнорировать тот факт, что я не переопределил метод getEvents (), который затем нарушил бы мое приложение в этом случае, тогда как использование интерфейса не позволило бы моему приложению работать, если ЛЮБОЙ из методов определенные в интерфейсе не существуют в классе, который его реализовал. Моему приложению не нужно заботиться о том, какой класс оно использует для получения данных из фида, только о наличии методов, необходимых для получения этих данных.

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

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

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

<?php
interface ImportableFeed 
{
    /**
     * @return array
     */
    public function getEvents();
}

Не так много места для сравнения абстрактных классов и интерфейсов. Интерфейсы - это просто карты, которые при реализации требуют, чтобы класс имел набор открытых интерфейсов.

7 голосов
/ 21 августа 2008

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

interface Readable {
  String read();
}

List<Readable> readables; // dunno what these actually are, but we know they have read();
for(Readable reader : readables)
  System.out.println(reader.read());

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

Языки с динамической типизацией имеют понятие "типизированной утки", когда вам не нужны интерфейсы; Вы можете предположить, что у объекта есть метод, который вы вызываете. Это решает проблему в статически типизированных языках, где у вашего объекта есть некоторый метод (в моем примере read ()), но не реализует интерфейс.

5 голосов
/ 21 августа 2008

По моему мнению, интерфейсы должны быть предпочтительнее нефункциональных абстрактных классов. Я не был бы удивлен, если бы там был даже удар производительности, поскольку есть только один экземпляр объекта, вместо того, чтобы анализировать два, объединяя их (хотя, я не могу быть уверен, я не знаком с внутренней работой) ООП PHP).

Это правда, что интерфейсы менее полезны / значимы, чем, скажем, Java. С другой стороны, PHP6 введет еще больше хинтинга типов, включая хинтинг типов для возвращаемых значений. Это должно добавить некоторую ценность интерфейсам PHP.

tl; dr: interfaces определяет список методов, которым необходимо следовать (например, API), тогда как абстрактный класс предоставляет некоторые базовые / общие функциональные возможности, которые подклассы уточняют для конкретных потребностей.

4 голосов
/ 21 августа 2008

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

В PHP вы можете применять несколько интерфейсов, разделяя их запятой (я думаю, я не считаю это чистым решением).

Что касается нескольких абстрактных классов, у вас может быть несколько абстрактов, расширяющих друг друга (опять же, я не совсем уверен в этом, но я думаю, что видел это где-то раньше). Единственное, что вы не можете продлить, это последний класс.

...