Интерфейс или абстрактный класс: какой использовать?


327

Пожалуйста, объясните, когда я должен использовать PHP interfaceи когда я должен использовать abstract class?

Как я могу изменить свой abstract classв interface?

Ответы:


458

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

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

Следует также помнить, что клиентские классы могут расширять только один абстрактный класс, тогда как они могут реализовывать несколько интерфейсов. Таким образом, если вы определяете свои контракты поведения в абстрактных классах, это означает, что каждый дочерний класс может соответствовать только одному контракту. Иногда это хорошо, когда вы хотите заставить своих программистов-пользователей следовать определенному пути. В других случаях это было бы плохо. Представьте себе, если бы PHP-интерфейсы Countable и Iterator были абстрактными классами, а не интерфейсами.

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


12
Я весь день пытался понять, как abstractи как используются interfaceзанятия, твой пост прояснил все.
Огромное

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

Так что во многих случаях мы должны использовать абстрактный класс из-за гибкости - это мой вывод :)
ymakux

3
@volocuga: не обязательно, как отметил Алан, можно расширить только один реферат. Мне лично не нравится, что абстракция реализует идею интерфейса, потому что она способствует запутыванию кода и является менее прямой, IMO.
префикс

171

Различия между Abstract Classи Interface:

Абстрактные классы

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

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

  • Дочерний класс, расширенный из абстрактного класса, должен быть логически связан.

Интерфейс

Интерфейс не может содержать никаких функций . Он содержит только определения методов.

  • Производный класс ДОЛЖЕН предоставлять код для всех методов, определенных в интерфейсе .

  • Совершенно разные и не связанные классы могут быть логически сгруппированы вместе с помощью интерфейса.


1
Можете ли вы привести пример из реальной жизни, чтобы продемонстрировать это?
Р.Н. Кушваха

1
Какая разница между abstract class X implements Yа class X implements Y?
Вебинан

3
@Webinan abstract class X implements YВы заявляете, что основная функциональность X должна быть реализована в производном классе, и что абстрактный и производный класс должны содержать функции, определенные в Y, в то время как class X implements Yтолько подразумевается, что класс X должен содержать функции, определенные в Y. Если ваш интерфейс Y не предназначен для реализации каким-либо другим классом, кроме того, что XI фактически пропустит определение Y в качестве интерфейса и реализует только функции в Y как открытую / защищенную / частную абстрактную функцию, чтобы убедиться, что они реализованы в производном классе.
Калле Бергстрем

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

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

128

Зачем использовать абстрактные классы? Ниже приведен простой пример. Допустим, у нас есть следующий код:

<?php 

class Fruit {
    private $color;

    public function eat() {
        // chew
    }

    public function setColor($c) {
        $this->color = $c;
    }
}

class Apple extends Fruit {
    public function eat() {
        // chew until core
    }
}

class Orange extends Fruit {
    public function eat() {
        // peeling
        // chew
    }
}

Теперь я дам тебе яблоко, а ты его съешь. Как это на вкус? На вкус как яблоко.

<?php 
$apple = new Apple();
$apple->eat();

// Now I give you a fruit.
$fruit = new Fruit();
$fruit->eat();

Что это на вкус? Ну, это не имеет особого смысла, поэтому вы не должны этого делать. Это достигается путем создания абстрактного класса Fruit и метода eat внутри него.

<?php 
abstract class Fruit {
    private $color;

    abstract public function eat(){}

    public function setColor($c) {
        $this->color = $c;
    }
}
?>

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

Пример из реального мира:

<?php 
abstract class person {

    public $LastName;
    public $FirstName;
    public $BirthDate;

    abstract protected function write_info();
}

final class employee extends person{

    public $EmployeeNumber;
    public $DateHired;

    public function write_info(){
        //sql codes here
        echo "Writing ". $this->LastName . "'s info to emloyee dbase table <br>";   
    }
}

final class student extends person{

    public $StudentNumber;
    public $CourseName;

    public function write_info(){
        //sql codes here
        echo "Writing ". $this->LastName . "'s info to student dbase table <br>";
    }
}

///----------
$personA = new employee;
$personB = new student;

$personA->FirstName="Joe";
$personA->LastName="Sbody";

$personB->FirstName="Ben";
$personB->LastName="Dover";

$personA->write_info();
// Writing Sbody's info to emloyee dbase table
$personB->write_info();
// Writing Dover's info to student dbase table 

2
Привет, этот ответ, вероятно, получил отрицательные отзывы из-за того, как он отформатирован. Было бы хорошо, если бы он не был большим блоком кода (четыре пробела превращают что-то в блок кода, освобождают отступ для текста, чтобы извлечь его из блока), и если это было скопировано откуда-то (похоже, так) было бы вежливо отдать им должное.
Камило Мартин

9
Я люблю тебя за плод пример человека! С тех пор, как я начал изучать php, этот пример дает мне понять, спасибо
Raheel

23
+1 What does that taste like? Well, it doesn't make much sense, so you shouldn't be able to do that.Теперь я знаю тезисы!
Вебинан

Что делает finalключевое слово? Отличный пост, спасибо.
Гас

1
@VineeshKalarickal Что я не понимаю в примере Person, так это разница между: 1) использованием абстрактного класса Person (как в примере); 2) написать Person как стандартный класс и заставить Employee и Student переопределить метод write_info ().
Ferex 12.12.16

66

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


37

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

Например:

<?php
class parser implements parserDecoratorPattern {
    //...
}

Таким образом, любой, кто читает мой код (и кто знает, что такое шаблон декоратора), сразу же узнает: а) как я создаю свой синтаксический анализатор и б) сможет увидеть, какие методы используются для реализации шаблона декоратора.

Кроме того, и я могу быть здесь неосновным, не будучи программистом на Java / C ++ / etc, но здесь могут играть роль типы данных. Ваши объекты относятся к типу, и когда вы передаете их типу, это имеет значение программно. Перемещение ваших контрактных элементов в интерфейс диктует только типы, которые возвращают методы, но не базовый тип класса, который его реализует.

Уже поздно, и я не могу придумать лучшего примера псевдокода, но здесь идет:

<?php
interface TelevisionControls {};
class Remote implements TelevisionControls {};
class Spouse implements TelevisionControls {};
Spouse spouse = new Spouse();
Remote remote = new Remote();
isSameType = (bool)(remote == spouse)

1
Какой замечательный пример! ;)
Джоэл Мерфи

@ matt2000 Не сексист, И теперь работает и для однополых браков. Отличное редактирование. :)
Остин Хуген

4
Это потрясающий пример! Тем не менее, я не думаю, что вы можете создать экземпляр абстрактного класса, а скорее класса, который происходит от абстрактного класса
Ариэль Нгуен

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

2
Хотя пример забавный, мы не можем создать экземпляр абстрактного класса напрямую, пожалуйста, внесите изменения в пример, чтобы люди не могли считать его допустимым для использования в PHP.
saji89

16

Основное отличие состоит в том, что абстрактный класс может содержать реализацию по умолчанию, а интерфейс - нет.

Интерфейс - это контракт поведения без какой-либо реализации.


16

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

Если ваш английский не является родным, вы можете посмотреть, что такое абстракция и интерфейсы. И искать синонимы тоже.

И это может помочь вам как метафора:

ИНТЕРФЕЙС

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

Дело здесь

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

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


АБСТРАКЦИИ

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

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

Теперь вы хотите обменяться ингредиентами и шагами, и они ДОЛЖНЫ быть определены в новой версии этого торта. Это абстрактные методы которые должны быть определены для нового пирога, потому что в пироге должен быть фрукт, но какой? Таким образом, вы берете черные ягоды на этот раз. Готово.

Итак, вы расширили торт, следовали интерфейсу и абстрагировали от него шаги и ингредиенты.


1
Это было мое любимое сравнение всего, что связано с PHP. Это действительно имело смысл. Спасибо!
cbloss793

13

Чтобы добавить к некоторым из уже превосходных ответов:

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

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

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

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

  • класс может расширять суперкласс и реализовывать любое количество интерфейсов:

    class SubClass extends ParentClass implements Interface1, Interface2 {
        // ...
    }

Пожалуйста, объясните, когда я должен использовать интерфейс и когда я должен использовать абстрактный класс?

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

Используйте абстрактный класс, если вы хотите создать фундамент для других объектов (частично построенный класс). Класс, который расширяет ваш абстрактный класс, будет использовать некоторые свойства или методы, определенные / реализованные:

<?php
// interface
class X implements Y { } // this is saying that "X" agrees to speak language "Y" with your code.

// abstract class
class X extends Y { } // this is saying that "X" is going to complete the partial class "Y".
?>

Как я могу изменить свой абстрактный класс на интерфейс?

Вот упрощенный случай / пример. Удалите все детали реализации. Например, измените свой абстрактный класс с:

abstract class ClassToBuildUpon {
    public function doSomething() {
          echo 'Did something.';
    }
}

чтобы:

interface ClassToBuildUpon {
    public function doSomething();
}

12

С философской точки зрения:

  • Абстрактный класс представляет отношение "является". Допустим, у меня есть фрукты, ну, у меня был бы абстрактный класс Fruit, который разделяет общую ответственность и общее поведение.

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

С точки зрения кодирования:

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

  • Другое отличие состоит в том, что объект может реализовать столько интерфейсов, сколько вам нужно, но вы можете иметь только один абстрактный класс из-за «проблемы с бриллиантами» (ознакомьтесь здесь, чтобы узнать почему! Http://en.wikipedia.org/wiki/ Multiple_inheritance # The_diamond_problem )

Я, наверное, забываю некоторые моменты, но надеюсь, что это прояснит ситуацию.

PS: «это» / «должен делать» принес ответ Вивека Вермани, я не хотел украсть его ответ, просто использовать термины, потому что они мне понравились!


2
Я верю, что слово, которое ты ищешь, съедобно.
Трэвис Уэстон

1
На самом деле, я считаю, что это «глагол», «делающее слово»
Grizly

7

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

Класс должен представлять сущность, тогда как интерфейс должен представлять поведение.

Давайте возьмем пример. Монитор компьютера является сущностью и должен быть представлен как класс.

class Monitor{
    private int monitorNo;
}

Он предназначен для предоставления вам интерфейса дисплея, поэтому функциональность должна определяться интерфейсом.

interface Display{
    void display();
}

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


2
PHP не определяет типы возвращаемых данных, и ОП пометил этот вопрос какPHP
Purefan

1

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

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.