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

281

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

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

Теги:
oop
interface
abstract-class

11 ответов

404

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

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

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

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

  • 12
    Я весь день пытался понять использование abstract и interface классов, ваш пост прояснил все. Большое спасибо, Алан
  • 4
    Еще одним преимуществом абстрактных классов является возможность определять абстрактные защищенные методы . Не всегда полезно, но может пригодиться в некоторых архитектурах.
Показать ещё 2 комментария
131

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

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

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

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

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

Интерфейс

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

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

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

  • 1
    Можете ли вы привести пример из реальной жизни, чтобы продемонстрировать это?
  • 1
    В чем разница между abstract class X implements Y и class X implements Y ?
Показать ещё 4 комментария
103

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

<?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, а также метода съедания внутри него.

<?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 . " 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 . " 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 info to emloyee dbase table
$personB->write_info();
// Writing Dover info to student dbase table 
  • 2
    Привет, этот ответ, вероятно, получил отрицательные отзывы из-за того, как он отформатирован. Было бы хорошо, если бы это был не большой кодовый блок (четыре пробела превращают что-то в кодовый блок, разбирают текст, чтобы извлечь его из блока), и если это было скопировано откуда-то (это выглядит так) было бы вежливо отдать им должное.
  • 9
    Я люблю тебя за плод пример человека! Так как я начал изучать PHP, этот пример дает мне понять, спасибо большое
Показать ещё 4 комментария
57

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

35

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

Например:

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

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

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

Поздно, и я не могу придумать лучший пример psudo-code, но здесь идет:

<?php
interface TelevisionControls {};
class Remote implements TelevisionControls {};
class Spouse implements TelevisionControls {};
Spouse spouse = new Spouse();
Remote remote = new Remote();
isSameType = (bool)(remote == spouse)
  • 19
    +1 потому что пример смешной :)
  • 1
    Какой замечательный пример! ;)
Показать ещё 4 комментария
12

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

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

10

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

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

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

ИНТЕРФЕЙС

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

Дело здесь

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

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


АБСТРАКЦИИ

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

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

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

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

10

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • 2
    Я верю, что слово, которое ты ищешь, съедобно.
  • 1
    На самом деле, я считаю, что это «глагол», «делающее слово»
4

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

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

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

class Monitor{
    private int monitorNo;
}

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

interface Display{
    void display();
}

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

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

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню