Какой смысл интерфейсов в PHP?

224

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

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

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

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

тип машины
источник
4
Вы должны прочитать это: stackoverflow.com/a/384067/14673
Люк М
почти уверен, что это умственная помощь и помощь в общении. интерфейсы выступают в качестве отличных инструментов обучения для вашего API, поскольку они объединяют сервисы, которые ваш API предоставляет вместе, абстрактным способом, который можно прочитать, не зная о реализации. Это дорого, но, поскольку люди, которые знакомы с интерфейсом, уже могут использовать функции напрямую, не нуждаясь в классах, сохраняя указатель. без определенных языком интерфейсов для многих программистов может быть сложно планировать заранее, так как люди, привыкшие к «неопознанным языкам», часто предпочитают проектировать с интерфейсами, а не на бумаге.
Дмитрий

Ответы:

142

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

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

Крейг Н
источник
39
Можно также сказать, что * Интерфейсы предоставляют дизайн для класса с нулевой реализацией. * Абстрактные классы обеспечивают некоторый дизайн, с некоторой реализацией. Абстрактные классы наиболее полезны, когда дочерние классы имеют некоторые общие черты реализации, но отличаются в определенных реализациях.
Jrgns
@Craig, нет проблем с множественным наследованием, просто текущие языки недостаточно мощны, чтобы правильно их реализовать. «Проблемы» на самом деле могут быть решены довольно легко, например, указание явного пути наследования для наследуемых функций с тем же именем может разрешить дилемму алмаза.
Pacerier
15
Это не то, что интерфейсы вообще. Это не компромисс по множественному наследованию, а создание концептуального и абстрактного контракта для реализуемых объектов и для использования другими объектами / методами. Интерфейсы являются инструментом для полиморфизма, а не для прямого наследования.
Призрак Мадары
123

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

Джон Дауни
источник
Это заставило меня немного понять это. Это как с пространствами имен - так что общий код проще в использовании и без конфликтов. Проще, когда два человека проводят занятия на одной базе, верно?
RedClover
Разве нам не нужно отличать общую концепцию «интерфейса» от конкретных интерфейсов в языке, подобном PHP? Например, любая функция имеет «интерфейс», который определяет, как вы ее используете, и скрывает ее реализацию. Так что такой «договорный» интерфейс не требует специальной языковой функции. Поэтому языковая особенность должна быть для чего-то другого (или чего-то дополнительного).,
UuDdLrLrSs
70

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

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

«Проблема алмазов» (иногда называемая «смертельным алмазом смерти») - это неоднозначность, которая возникает, когда два класса 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) для вызова / использования.

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

Я надеюсь, что я не повторял себя слишком много.

Джо Смо
источник
Действительно хорошая аналогия с автомобильными педалями!
Джеймс
24

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

Сэм Макафи
источник
Это прямо здесь. Жестко предписывайте разработчикам использовать ваши методы, не расширяя и не перезаписывая их
Ник
21

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

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

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

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

Росс
источник
9
В PHP интерфейсы содержат только объявление метода, а не фактическую реализацию. Однако абстрактные классы позволяют «добавлять код» в методы, которые наследуются классами, которые его расширяют. Я полагаю, что это различие - то, что имел в виду mk.
nocash
13

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

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

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

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

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

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

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

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

catalin.costache
источник
10

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

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

<?php

interface ImportableFeed 
{
    public function getEvents();
}

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

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

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

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

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

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

Houghtelin
источник
Хорошее объяснение :) Спасибо!
Разв.432 15.09.15
Просто для добавления информации: В абстрактном классе, если вы объявляете метод как абстрактный, он ведет себя как интерфейс, поэтому вы не можете игнорировать тот факт, что вы не переопределяете getEvents (). Приложение не будет работать так же, как с интерфейсом.
Rikudou_Sennin
8

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

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 ()), но не реализован интерфейс.

Outlaw Programmer
источник
6

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

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

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

Хенрик Пол
источник
PHP 6 никогда не выйдет. PHP 6 был проектом, который разрабатывался с 2005 по 2010 год, но был отложен и в конечном итоге отменен. PHP 7 является следующей версией, в основном, чтобы избежать путаницы с прежним проектом PHP 6.
Ашу Джа
5

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

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

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

Росс
источник
4

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

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

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

Тот факт, что ваш вопрос касается PHP, делает вещи немного более интересными. Печатание на интерфейсах все еще не является чрезвычайно необходимым в PHP, где вы можете в значительной степени передать любой метод, независимо от его типа. Вы можете статически набирать параметры метода, но некоторые из них не работают (я думаю, что String вызывает некоторые сбои). Соедините это с тем фактом, что вы не можете печатать большинство других ссылок, и нет особой ценности в попытках принудительной статической типизации в PHP ( на данный момент ). И из-за этого, значение интерфейсов в PHP , на данный моментгораздо меньше, чем в более строго типизированных языках. Они имеют преимущество читабельности, но мало что еще. Многократная реализация даже не выгодна, потому что вы все равно должны объявлять методы и предоставлять им тела в реализаторе.

Брайан Варшоу
источник
2

Ниже приведены пункты для интерфейса PHP

  1. Он используется для определения обязательных методов в классе [если вы хотите загрузить html, тогда требуется идентификатор и имя, поэтому в этом случае интерфейс включает setID и setName].
  2. Интерфейс строго заставляет класс включать все методы, определенные в нем.
  3. Вы можете определить метод только в интерфейсе с общедоступной доступностью.
  4. Вы также можете расширить интерфейс, как класс. Вы можете расширить интерфейс в php используя ключевое слово extends.
  5. Расширить несколько интерфейсов.
  6. Вы не можете реализовать 2 интерфейса, если оба используют одну и ту же функцию. Это выдаст ошибку.

Пример кода:

interface test{
    public function A($i);
    public function B($j = 20);
}

class xyz implements test{
    public function A($a){
        echo "CLASS A Value is ".$a;
    }
    public function B($b){
        echo "CLASS B Value is ".$b;
    }
}
$x = new xyz();
echo $x->A(11);
echo "<br/>";
echo $x->B(10);
Вивек с Вамей
источник
2

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

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

2.Все метода в интерфейсе должна быть в общественной области видимости.

3. Класс может реализовывать более одного интерфейса, в то время как он может наследовать только от одного абстрактного класса.

                                  interface                      abstract class
the code                     - abstract methods               - abstract methods
                             - constants                      - constants                  
                                                              - concrete methods
                                                              - concrete variables

access modifiers             
                             - public                         - public
                                                              - protected
                                                              - private
                                                                etc.
number of parents          The same class can implement
                           more than 1 interface              The child class can 
                                                              inherit only from 1 abstract class

Надеюсь, что это поможет любому понять!

Хирен Гоэль
источник
2

Интерфейсы как твои гены.

Абстрактные классы как твои настоящие родители.

Их цели наследственные, но в случае абстрактных классов и интерфейсов то, что наследуется, более конкретно.

Tapha
источник
2

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

Интерфейс работает как «контракты», определяя, что делает набор подклассов, но не как они это делают.

Правило

  1. Интерфейс не может быть создан.

  2. Вы не можете реализовать какой-либо метод в интерфейсе, то есть он содержит только сигнатуру метода, но не детали (тело).

  3. Интерфейсы могут содержать методы и / или константы, но без атрибутов. Константы интерфейса имеют те же ограничения, что и константы класса. Методы интерфейса неявно абстрактны.

  4. Интерфейсы не должны объявлять конструкторы или деструкторы, так как это детали реализации на уровне класса.

  5. Все методы в интерфейсе должны иметь публичную видимость.

Теперь давайте возьмем пример. Предположим, у нас есть две игрушки: одна - собака, а другая - кошка.

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

Когда пользователь нажимает кнопку «говорить», игрушка должна говорить, не важно, собака это или кошка.

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

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

Сообщество
источник