Який сенс інтерфейсів у PHP?


224

Інтерфейси дозволяють створювати код, який визначає методи класів, які його реалізують. Однак ви не можете додати жодний код до цих методів.

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

Тепер, якщо ви можете досягти тієї ж мети за допомогою абстрактних класів, навіщо нам взагалі потрібна концепція інтерфейсів?

Мені сказали, що це має відношення до теорії OO від C ++ до Java, на чому базується робота в PHO OO. Чи корисна ця концепція в Java, але не в PHP? Це просто спосіб утриматися від того, щоб заповнювачі заповнювались абстрактним класом? Я щось пропускаю?


4
Ви повинні прочитати це: stackoverflow.com/a/384067/14673
Люк М

майже впевнений, що це психічна допомога та комунікаційна допомога. інтерфейси виступають відмінними інструментами навчання для вашої програми, оскільки вони поєднують послуги, які ваш api представляє разом, абстрактним чином, який можна прочитати, не знаючи про реалізацію. Це дорого, але оскільки люди, яким комфортно користуватися інтерфейсом, вже можуть використовувати функції безпосередньо, не потребуючи занять, тим самим економлячи покажчик. без визначених мовою інтерфейсів може бути складно планувати заздалегідь для багатьох програмістів, оскільки люди, що звикли до "oop languages", частіше віддають перевагу проектуванню з інтерфейсами, а не на папері.
Дмитро

Відповіді:


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(). Уявіть, що кожна марка автомобіля мала б ці "методи" відрізнятися від іншої марки. BMW мав гальмо з правого боку, а Honda мав гальмо на лівій стороні колеса. Люди повинні були б дізнатися, як працюють ці "методи" щоразу, коли вони купують іншу марку автомобіля. Ось чому корисно мати один і той же інтерфейс у кількох "місцях".

Що для вас робить інтерфейс (чому б хтось навіть користувався ним)? Інтерфейс заважає вам робити "помилки" (це запевняє вас, що всі класи, які реалізують певний інтерфейс, будуть мати методи, які є в інтерфейсі).

// 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()(у цьому прикладі) і $personObjectдля отримання виклику / використання йому знадобиться лише 1 параметр ( ).

Клас, який реалізує інтерфейс, повинен реалізувати всі методи, які інтерфейс робить / має.

Я сподіваюся, що я не надто повторив себе.


Дійсно приємна аналогія з педалями автомобіля!
Джеймс

24

Різниця між використанням інтерфейсу та абстрактного класу для мене більше стосується організації коду, ніж примусового виконання самою мовою. Я їх багато використовую, коли готую код для роботи з іншими розробниками, щоб вони залишалися в межах запланованих моделей дизайну. Інтерфейси - це свого роду "дизайн за контрактом", згідно з яким ваш код погоджується відповідати на встановлений набір викликів API, які можуть надходити з коду, у якого ви не маєте доступу.

Хоча успадкування від абстрактного класу є відношенням "є", це не завжди те, що ви хочете, а реалізація інтерфейсу - це скоріше відношення "діє як". Ця різниця може бути досить суттєвою у певних контекстах.

Наприклад, скажімо, у вас є абстрактний обліковий запис класу, з якого поширюється багато інших класів (типи рахунків тощо). Він має певний набір методів, застосовних лише до цієї групи типів. Однак деякі з цих підкласів облікового запису реалізують Versionable, або Listable, або Editable, щоб їх можна було кинути в контролери, які розраховують використовувати ці API. Контролеру байдуже, що це за тип об’єкта

На відміну від цього, я також можу створити об’єкт, який не поширюється на обліковий запис, скажімо, абстрактний клас користувача, і все ще реалізувати Listable and Editable, але не Versionable, що тут не має сенсу.

Таким чином, я кажу, що підклас FooUser НЕ є обліковим записом, але діє як об’єкт Editable. Так само BarAccount поширюється на обліковий запис, але не є підкласом користувача, але реалізує редаговані, перелічені, а також універсальні.

Додавання всіх цих API для редагування, переліку та версії до самих абстрактних класів було б не лише захаращеним і некрасивим, а й дублюватиме загальні інтерфейси в обліковому записі та користувачеві, або змушує мій користувальницький об’єкт реалізовувати універсальний, ймовірно, просто кинути виняток.


Це саме тут. Жорстко примушуйте розробників використовувати ваші методи, не розширюючи чи перезаписуючи їх
Nick

21

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

Я не впевнений, що ви маєте на увазі під тим, що не можете додати код до методів - тому що можете. Ви застосовуєте інтерфейс до абстрактного класу або до класу, який його розширює?

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

Я завжди вважав інтерфейси як зразок для зовнішніх розробників або додатковий набір правил, щоб переконатися, що все правильно.


9
У PHP інтерфейси містять лише декларацію методу, а не фактичну реалізацію. Абстрактні класи, однак, дозволяють "додати код" до методів, які мають бути успадковані класами, що розширюють його. Я вважаю, що ця різниця - це те, про що мав на увазі mk.
nocash

13

Ви будете використовувати інтерфейси в PHP:

  1. Щоб приховати реалізацію - встановіть протокол доступу до класу об'єктів та змініть базову реалізацію без рефакторингу у всіх місцях, якими ви використовували ці об'єкти
  2. Щоб перевірити тип - як у переконанні, що параметр має певний тип $object instanceof MyInterface
  3. Для забезпечення перевірки параметрів під час виконання
  4. Реалізація декількох форм поведінки в одному класі (побудова складних типів)

    Автомобільний клас реалізує EngineInterface, BodyInterface, SteeringInterface {

так що Carоб'єкт ca зараз start(), stop()(EngineInterface) або goRight(), goLeft()(Інтерфейс керування)

та інші речі, про які я зараз не можу думати

Число 4 - це, мабуть, найбільш очевидний випадок використання, з яким ви не можете звернутися до абстрактних класів.

З роздумів на Java:

Інтерфейс говорить: "Ось так виглядатимуть усі класи, які реалізують цей конкретний інтерфейс". Таким чином, будь-який код, який використовує певний інтерфейс, знає, які методи можна викликати для цього інтерфейсу, і це все. Отже, інтерфейс використовується для встановлення "протоколу" між класами.


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();
}

Немало місця для порівняння абстрактних класів та інтерфейсів. Інтерфейси - це просто карти, які при реалізації вимагають, щоб клас мав набір загальнодоступних інтерфейсів.


Гарне пояснення :) Дякую!
Разван.432

Просто для додання інформації: Якщо в абстрактному класі ви оголошуєте метод абстрактним, він поводиться як інтерфейс, тому ви не можете ігнорувати той факт, що ви не перекрили 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 ()), але не реалізує інтерфейс.


6

На мою думку, слід віддавати перевагу інтерфейсам над нефункціональними абстрактними класами. Я не був би здивований, якби там було навіть хіт продуктивності, оскільки існує лише один об'єкт, інстанційований замість розбору двох, об'єднання їх (хоча, я не можу бути впевнений, я не знайомий із внутрішніми роботами OOP PHP).

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

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


PHP 6 ніколи не вийде. PHP 6 був проектом, який розроблявся з 2005-2010 рр., Але він був відкладений і в кінцевому рахунку скасований. PHP 7 - наступна версія, в основному для уникнення плутанини з колишнім проектом PHP 6.
Ашу Джа

5

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

У PHP ви можете застосувати кілька інтерфейсів, розділивши їх комою (я думаю, я не вважаю це чистим рішенням).

Щодо кількох абстрактних класів, у вас може бути кілька тез, що розширюють один одного (знову ж, я не зовсім впевнений у цьому, але, думаю, я бачив це десь раніше). Єдине, що ви не можете продовжити - це заключний клас.


4

Інтерфейси не дадуть вашому коду підвищити продуктивність або щось подібне, але вони можуть пройти довгий шлях до того, щоб зробити його бездоганним. Це правда, що для встановлення інтерфейсу до вашого коду може бути використаний абстрактний клас (або навіть неабразований клас), але правильні інтерфейси (ті, які ви визначаєте за ключовим словом і містять лише підписи методів) просто простіші перебирати і читати.

Попри це, я схильний використовувати розсуд, вирішуючи, чи використовувати інтерфейс над класом чи ні. Іноді я хочу реалізувати методи за замовчуванням або змінні, які будуть спільними для всіх підкласів.

Зрозуміло, сенс щодо впровадження декількох інтерфейсів теж є здоровим. Якщо у вас є клас, який реалізує кілька інтерфейсів, ви можете використовувати об’єкт цього класу як різні типи в одній програмі.

Той факт, що ваше запитання стосується PHP, робить речі дещо цікавішими. Введення в інтерфейси все ще не є надзвичайно необхідним у PHP, де ви можете в значній мірі подати будь-що в будь-який спосіб, незалежно від його типу. Ви можете статично вводити параметри методу, але деякі з них порушені (Я вважаю, що струна викликає деякі гикавки). Поєднайте це з тим, що ви не можете вводити більшість інших посилань, і немає великої цінності в спробі примусити статичне введення тексту в PHP ( на даний момент ). І через це, значення інтерфейсів у PHP , на даний моментнабагато менше, ніж у більш сильно набраних мовах. Вони мають перевагу читабельності, але мало іншого. Багаторазова реалізація навіть не вигідна, тому що вам все-таки потрібно оголосити методи та надати їм органи в межах виконавця.


2

Нижче наведено точки інтерфейсу PHP

  1. Він використовується для визначення необхідного відсутності методів у класі [якщо ви хочете завантажити html, тоді потрібні id та ім'я, тому в цьому випадку інтерфейс включає setID та setName].
  2. Інтерфейс суворо примусовий клас, щоб включити в нього всі визначені методи.
  3. Ви можете визначити метод лише в інтерфейсі з загальнодоступною доступністю.
  4. Ви також можете розширити інтерфейс, як клас. Ви можете розширити інтерфейс у php, використовуючи ключове слово extens.
  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

Інтерфейси схожі на ваші гени.

Абстрактні класи схожі на ваших справжніх батьків.

Їх цілі є спадковими, але у випадку абстрактних класів проти інтерфейсів те, що успадковується, є більш конкретним.


2

Я не знаю про інші мови, що таке поняття інтерфейсу. Але для PHP я постараюся це пояснити. Будьте терплячі, і будь ласка, прокоментуйте, якщо це допомогло.

Інтерфейс працює як "контракти", вказуючи, що робить набір підкласів, але не те, як вони це роблять.

Правило

  1. Інтерфейс неможливо створити.

  2. Ви не можете реалізувати жоден метод в інтерфейсі, тобто він містить лише. Підпис методу, але не деталі (тіло).

  3. Інтерфейси можуть містити методи та / або константи, але ніяких атрибутів. Константи інтерфейсу мають ті ж обмеження, що і константи класу. Методи інтерфейсу неявно абстрактні.

  4. Інтерфейси не повинні оголошувати конструкторів або деструкторів, оскільки це деталі реалізації на рівні класу.

  5. Усі методи в інтерфейсі повинні мати публічну видимість.

Тепер візьмемо приклад. Припустимо, у нас є дві іграшки: одна - Собака, а друга - Кішка.

Як ми знаємо, собака гавкає, а котячий мевс. Ці двоє мають однаковий метод розмови, але з різною функціональністю або реалізацією. Припустимо, ми надаємо користувачеві пульт дистанційного керування, який має кнопку розмови.

Коли користувач натискає кнопку розмови, іграшка повинна говорити, це не має значення, чи це собака чи кішка.

Це хороший випадок для використання інтерфейсу, а не абстрактного класу, оскільки реалізація відрізняється. Чому? Пам'ятайте

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.