Черты против интерфейсов


346

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

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


6
Интерфейс не имеет никакого кода в теле функций. у них фактически нет никаких функциональных тел.
Хакре

2
Несмотря на мой очень одобренный ответ, я хотел бы заявить для отчета, что я вообще против черты / mixin . Посмотрите эту стенограмму чата, чтобы узнать, как черты часто подрывают основную практику ООП .
rdlowrey

2
Я бы сказал, наоборот. Работая с PHP много лет до и после появления черт, я думаю, легко доказать их ценность. Просто прочитайте этот практический пример, который позволяет «образным моделям» также ходить и разговаривать как Imagickобъекты, за исключением всего раздувания, необходимого в прежние времена до появления черт характера.
quickshiftin

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

Ответы:


239

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

Когда признак used, реализации методов также встречаются - что не происходит в Interface.

Это самая большая разница.

Из горизонтального повторного использования для PHP RFC :

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


2
@JREAM На практике ничего. На самом деле гораздо больше.
Алек Ущелье

79
За исключением того, что черты вообще не являются интерфейсами. Интерфейсы - это спецификация, которую можно проверить. Черты не могут быть проверены, поэтому они являются только реализацией. Они являются полной противоположностью интерфейсов. Эта строка в RFC просто неверна ...
ircmaxell

196
Черты, по сути, с помощью языка копировать и вставлять .
Шахид

10
Это не метафора. Это убивает смысл слова. Это похоже на описание коробки как поверхности с объемом.
Cleong

6
Чтобы раскрыть комментарии ircmaxell и Shadi: Вы можете проверить, реализует ли объект интерфейс (через instanceof), и убедиться, что аргумент метода реализует интерфейс через подсказку типа в сигнатуре метода. Вы не можете выполнить соответствующие проверки на черты.
Брайан д'Астус

531

Объявление о государственной службе:

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


Давайте начнем с того, что скажем так:

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

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

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

Интерфейсы

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

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

В качестве примера рассмотрим реальный сценарий (без машин и виджетов):

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

Вы начинаете с написания класса для кэширования ответов на запросы с использованием APC:

class ApcCacher
{
  public function fetch($key) {
    return apc_fetch($key);
  }
  public function store($key, $data) {
    return apc_store($key, $data);
  }
  public function delete($key) {
    return apc_delete($key);
  }
}

Затем в своем объекте ответа HTTP вы проверяете наличие попадания в кэш, прежде чем выполнять всю работу по генерации фактического ответа:

class Controller
{
  protected $req;
  protected $resp;
  protected $cacher;

  public function __construct(Request $req, Response $resp, ApcCacher $cacher=NULL) {
    $this->req    = $req;
    $this->resp   = $resp;
    $this->cacher = $cacher;

    $this->buildResponse();
  }

  public function buildResponse() {
    if (NULL !== $this->cacher && $response = $this->cacher->fetch($this->req->uri()) {
      $this->resp = $response;
    } else {
      // Build the response manually
    }
  }

  public function getResponse() {
    return $this->resp;
  }
}

Этот подход прекрасно работает. Но, может быть, через несколько недель вы решите использовать файловую кеш-систему вместо APC. Теперь вам нужно изменить код контроллера, потому что вы запрограммировали свой контроллер для работы с функциональностью ApcCacherкласса, а не с интерфейсом, который выражает возможности ApcCacherкласса. Допустим, вместо вышесказанного вы сделали Controllerкласс зависимым, а CacherInterfaceне конкретным, ApcCacherвот так:

// Your controller's constructor using the interface as a dependency
public function __construct(Request $req, Response $resp, CacherInterface $cacher=NULL)

Для этого вы определяете свой интерфейс следующим образом:

interface CacherInterface
{
  public function fetch($key);
  public function store($key, $data);
  public function delete($key);
}

В свою очередь у вас есть и ваш, ApcCacherи ваши новые FileCacherклассы, реализующие, CacherInterfaceи вы программируете свой Controllerкласс, чтобы использовать возможности, требуемые интерфейсом.

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

Черты

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

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

Рассмотрим следующую черту реализации:

interface Person
{
    public function greet();
    public function eat($food);
}

trait EatingTrait
{
    public function eat($food)
    {
        $this->putInMouth($food);
    }

    private function putInMouth($food)
    {
        // Digest delicious food
    }
}

class NicePerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Good day, good sir!';
    }
}

class MeanPerson implements Person
{
    use EatingTrait;

    public function greet()
    {
        echo 'Your mother was a hamster!';
    }
}

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

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


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

35
«[C] выражение характеристик, которые соответствуют возможностям, требуемым интерфейсом в данном классе, является идеальным вариантом использования». Точно: +1
Алек Ущелье

5
Было бы справедливо сказать, что черты в PHP похожи на миксины в других языках?
Ино

5
@igorpan Для всех намерений и целей , я бы сказал , реализация чертой РНР является такой же , как множественное наследование. Стоит отметить, что если признак в PHP задает статические свойства, то каждый класс, использующий эту особенность, будет иметь свою собственную копию статического свойства. Что еще более важно ... видя, как этот пост в настоящее время чрезвычайно высоко в поисковой выдаче при запросе черт, я собираюсь добавить объявление публичной службы в верхней части страницы. Вы должны прочитать это.
rdlowrey

3
+1 для подробного объяснения. Я родом из рубина, где миксины используются МНОГО; просто добавив мои два цента, хорошее правило, которое мы используем, можно перевести в php как «не реализовывать методы, которые изменяют $ this в чертах». Это предотвращает целую кучу сумасшедших сеансов отладки ... Миксин также НЕ ДОЛЖЕН делать какие-либо предположения относительно класса, в который он будет смешиваться (или вы должны сделать это очень ясно и уменьшить зависимости до минимума). В связи с этим я нахожу ваше представление о чертах реализации интерфейсов приятным.
m_x

67

A trait- это, по сути, реализация PHP в a mixin, и, по сути, это набор методов расширения, которые могут быть добавлены в любой класс путем добавления trait. Методы затем становятся частью реализации этого класса, но без использования наследования .

Из руководства по PHP (выделено мое):

Черты - это механизм повторного использования кода в языках с одним наследованием, таких как PHP. ... Это дополнение к традиционному наследованию и позволяет горизонтальную композицию поведения; то есть применение членов класса без необходимости наследования.

Пример:

trait myTrait {
    function foo() { return "Foo!"; }
    function bar() { return "Bar!"; }
}

Определив вышеуказанную черту, я могу сделать следующее:

class MyClass extends SomeBaseClass {
    use myTrait; // Inclusion of the trait myTrait
}

На этом этапе, когда я создаю экземпляр класса MyClass, у него есть два метода, называемые foo()и bar()- которые берутся myTrait. И - обратите внимание, что trait-определенные методы уже имеют тело метода, чего не Interfaceможет определить -определенный метод.

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

Несколько вещей, на которые стоит обратить внимание:

                      -----------------------------------------------
                      |   Interface   |  Base Class   |    Trait    |
                      ===============================================
> 1 per class         |      Yes      |       No      |     Yes     |
---------------------------------------------------------------------
Define Method Body    |      No       |       Yes     |     Yes     |
---------------------------------------------------------------------
Polymorphism          |      Yes      |       Yes     |     No      |
---------------------------------------------------------------------

Полиморфизм:

В предыдущем примере, в котором MyClass проходит SomeBaseClass , MyClass является экземпляром SomeBaseClass. Другими словами, такой массив SomeBaseClass[] basesможет содержать экземпляры MyClass. Точно так же, если MyClassрасширен IBaseInterface, массив IBaseInterface[] basesможет содержать экземпляры MyClass. Нет такой полиморфной конструкции, доступной с trait- потому что по traitсути это просто код, который для удобства программиста копируется в каждый класс, который его использует.

Внеочередные:

Как описано в руководстве:

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

Итак - рассмотрим следующий сценарий:

class BaseClass {
    function SomeMethod() { /* Do stuff here */ }
}

interface IBase {
    function SomeMethod();
}

trait myTrait {
    function SomeMethod() { /* Do different stuff here */ }
}

class MyClass extends BaseClass implements IBase {
    use myTrait;

    function SomeMethod() { /* Do a third thing */ }
}

При создании экземпляра MyClass, выше, происходит следующее:

  1. Interface IBaseТребует функцию без параметров под названием SomeMethod()должны быть предоставлены.
  2. Базовый класс BaseClassобеспечивает реализацию этого метода - удовлетворение потребности.
  3. trait myTraitОбеспечивает функцию без параметров , называемой SomeMethod()так же, которая имеет приоритет над BaseClass-версиями
  4. class MyClassПредоставляет свою собственную версию SomeMethod()- которая имеет приоритет над trait-версиями.

Вывод

  1. An Interfaceне может обеспечить реализацию по умолчанию тела метода, в то время как traitможет.
  2. An Interfaceявляется полиморфной , унаследованной конструкцией, а a trait- нет.
  3. Несколько Interfaces могут использоваться в одном классе, также как и несколько traits.

4
«Черта похожа на концепцию абстрактного класса в C #» Нет, абстрактный класс - это абстрактный класс; эта концепция существует как в PHP, так и в C #. Я бы сравнил признак в PHP со статическим классом, состоящим из методов расширения в C #, с устранением ограничения на основе типов, поскольку признак может использоваться практически любым типом, в отличие от метода расширения, который расширяет только один тип.
BoltClock

1
Очень хороший комментарий - и я с вами согласен. В перечитывании это лучшая аналогия. Я полагаю, что все же лучше думать об этом как о mixin- и, поскольку я вновь обратился к открытию своего ответа, я обновился, чтобы отразить это. Спасибо за комментарий, @BoltClock!
Трой Алфорд

1
Я не думаю, что есть какое-либо отношение к методам расширения c #. Методы расширения добавляются к единственному типу класса (конечно, с учетом иерархии классов), их цель состоит в том, чтобы расширить тип дополнительными функциями, а не «делиться кодом» между несколькими классами и создавать беспорядок. Это не сравнимо! Если что-то нужно использовать повторно, это обычно означает, что у него должно быть свое собственное пространство, например, отдельный класс, который будет связан с классами, которым требуется общая функциональность. Реализация может варьироваться в зависимости от дизайна, но примерно это так. Черты - это еще один способ сделать плохой код.
София

Класс может иметь несколько интерфейсов? Я не уверен, правильно ли я понимаю ваш график, но класс X реализует Y, Z допустим.
Янн Шабо

26

Я думаю traits, что полезно создавать классы, которые содержат методы, которые можно использовать как методы нескольких разных классов.

Например:

trait ToolKit
{
    public $errors = array();

    public function error($msg)
    {
        $this->errors[] = $msg;
        return false;
    }
}

Вы можете использовать и использовать этот метод «error» в любом классе, который использует эту черту.

class Something
{
    use Toolkit;

    public function do_something($zipcode)
    {
        if (preg_match('/^[0-9]{5}$/', $zipcode) !== 1)
            return $this->error('Invalid zipcode.');

        // do something here
    }
}

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

Это совершенно другое!


Я думаю, что это плохой пример черты. to_integerбыло бы более вероятно включить в IntegerCastинтерфейс, потому что нет принципиально подобного способа (интеллектуального) приведения классов к целому числу.
Мэтью

5
Забудьте "to_integer" - это просто иллюстрация. Пример. «Привет, мир». "Example.com".
Дж. Бруни

2
Какую выгоду дает эта черта инструментария, чего не может отдельный класс утилит? Вместо use Toolkitвас может быть $this->toolkit = new Toolkit();или я упускаю какую-то выгоду от самой черты?
Энтони

@ Энтони где-то в вашем Somethingконтейнере вы делаетеif(!$something->do_something('foo')) var_dump($something->errors);
TheRealChx101

20

Для начинающих выше ответ может быть трудным, это самый простой способ понять это:

Черты

trait SayWorld {
    public function sayHello() {
        echo 'World!';
    }
}

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

class MyClass{
  use SayWorld;

}

$o = new MyClass();
$o->sayHello();

Круто верно!

Не только функции, которые вы можете использовать что-либо в признаке (функция, переменные, const ..). Также вы можете использовать несколько черт:use SayWorld,AnotherTraits;

Интерфейс

  interface SayWorld {
     public function sayHello();
  }

  class MyClass implements SayWorld { 
     public function sayHello() {
        echo 'World!';
     }
}

так вот как интерфейс отличается от черт: вы должны воссоздать все в интерфейсе в реализованном классе. Интерфейс не имеет реализации. и интерфейс может иметь только функции и const, он не может иметь переменных.

Надеюсь, это поможет!


5

Часто используемая метафора для описания черт: черты - это интерфейсы с реализацией.

Это хороший способ думать об этом в большинстве случаев, но между ними есть ряд тонких различий.

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

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

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

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

class SlidingDoor extends Door implements IKeyed  
{  
    use KeyedTrait;  
    [...] // Generally not a lot else goes here since it's all in the trait  
}

Это означает, что вы можете использовать instanceof, чтобы определить, является ли конкретный объект Door ключевым или нет, вы знаете, что получите согласованный набор методов и т. Д., И весь код находится в одном месте во всех классах, которые используют KeyedTrait.


Последняя часть этого ответа, конечно, о том, что @rdlowrey говорит более подробно в последних трех абзацах под заголовком «Черты» в своем посте; Я просто чувствовал, что очень простой фрагмент кода скелета поможет проиллюстрировать это.
Джон Клоске

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

4

Черты просто для повторного использования кода .

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

Для справки - http://www.php.net/manual/en/language.oop5.traits.php


3

В принципе, вы можете рассматривать черту как автоматическое «копирование-вставка» кода.

Использование черт опасно, так как нет смысла знать, что он делает до исполнения.

Тем не менее, признаки являются более гибкими из-за отсутствия таких ограничений, как наследование.

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

Для тех, кто читает по-французски и может его получить, в журнале GNU / Linux HS 54 есть статья на эту тему.


Все еще не понимаю, как черты отличаются от интерфейсов с реализацией по умолчанию
denis631

@ denis631 Черты можно видеть как фрагменты кода, а интерфейсы - как подписные контракты. Если это может помочь, вы можете рассматривать его как неформальный фрагмент класса, который может содержать все, что угодно. Дайте мне знать, если это поможет.
Benj

Я вижу, что признаки PHP можно рассматривать как макросы, которые затем раскрываются во время компиляции / просто совмещают фрагмент кода с этим ключом. Однако черты ржавчины кажутся разными (или я ошибаюсь). Но так как у них обоих есть черта слова, я бы предположил, что они одинаковы, что означает одно и то же понятие. Ссылка на черты ржавчины: doc.rust-lang.org/rust-by-example/trait.html
denis631

2

Если вы знаете английский и знаете, что это traitзначит, это именно то, что говорит название. Это набор классов и свойств без классов, которые вы присоединяете к существующим классам путем ввода use.

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


2

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

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

Добавление возможностей публикации / подписки на события в класс может быть распространенным сценарием в некоторых основах кода. Есть 3 общих решения:

  1. Определите базовый класс с кодом события pub / sub, а затем классы, которые хотят предлагать события, могут расширить его, чтобы получить возможности.
  2. Определите класс с кодом публикации / вложенного события, и тогда другие классы, которые хотят предлагать события, могут использовать его через композицию, определяя свои собственные методы для обертывания составного объекта, передавая вызовы методов к нему.
  3. Определите признак с помощью паба / подкода событий, а затем другие классы, которые хотят предлагать события, могут useчертить, или импортировать их, чтобы получить возможности.

Насколько хорошо работает каждый?

# 1 не работает хорошо Так будет до того дня, когда вы поймете, что не можете расширить базовый класс, потому что вы уже расширяете что-то еще. Я не буду показывать пример этого, потому что должно быть очевидно, насколько ограничено использование наследования, подобного этому.

# 2 и # 3 оба работают хорошо. Я покажу пример, который подчеркивает некоторые различия.

Во-первых, некоторый код, который будет одинаковым в обоих примерах:

Интерфейс

interface Observable {
    function addEventListener($eventName, callable $listener);
    function removeEventListener($eventName, callable $listener);
    function removeAllEventListeners($eventName);
}

И немного кода для демонстрации использования:

$auction = new Auction();

// Add a listener, so we know when we get a bid.
$auction->addEventListener('bid', function($bidderName, $bidAmount){
    echo "Got a bid of $bidAmount from $bidderName\n";
});

// Mock some bids.
foreach (['Moe', 'Curly', 'Larry'] as $name) {
    $auction->addBid($name, rand());
}

Хорошо, теперь давайте покажем, как реализация Auctionкласса будет отличаться при использовании черт.

Во-первых, вот как будет выглядеть # 2 (с использованием композиции):

class EventEmitter {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    private $eventEmitter;

    public function __construct() {
        $this->eventEmitter = new EventEmitter();
    }

    function addBid($bidderName, $bidAmount) {
        $this->eventEmitter->triggerEvent('bid', [$bidderName, $bidAmount]);
    }

    function addEventListener($eventName, callable $listener) {
        $this->eventEmitter->addEventListener($eventName, $listener);
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventEmitter->removeEventListener($eventName, $listener);
    }

    function removeAllEventListeners($eventName) {
        $this->eventEmitter->removeAllEventListeners($eventName);
    }
}

Вот как будет выглядеть # 3 (черты):

trait EventEmitterTrait {
    private $eventListenersByName = [];

    function addEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName][] = $listener;
    }

    function removeEventListener($eventName, callable $listener) {
        $this->eventListenersByName[$eventName] = array_filter($this->eventListenersByName[$eventName], function($existingListener) use ($listener) {
            return $existingListener === $listener;
        });
    }

    function removeAllEventListeners($eventName) {
        $this->eventListenersByName[$eventName] = [];
    }

    protected function triggerEvent($eventName, array $eventArgs) {
        foreach ($this->eventListenersByName[$eventName] as $listener) {
            call_user_func_array($listener, $eventArgs);
        }
    }
}

class Auction implements Observable {
    use EventEmitterTrait;

    function addBid($bidderName, $bidAmount) {
        $this->triggerEvent('bid', [$bidderName, $bidAmount]);
    }
}

Обратите внимание, что код внутри EventEmitterTraitточно такой же, как и внутри EventEmitterкласса, за исключением того, что признак объявляет triggerEvent()метод защищенным. Таким образом, единственное отличие, на которое вам нужно обратить внимание, - это реализация Auctionкласса .

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

Однако могут быть случаи, когда вы не хотите, чтобы ваш Auctionкласс реализовывал полный Observableинтерфейс - может быть, вы хотите предоставить только 1 или 2 метода, или, возможно, вообще ни одного, чтобы вы могли определить свои собственные сигнатуры методов. В таком случае вы все равно можете предпочесть метод композиции.

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

* На самом деле вы могли бы сделать и то и другое - определить EventEmitterкласс на тот случай, если вы когда-нибудь захотите использовать его композиционно, а также определить EventEmitterTraitчерту, используя EventEmitterреализацию класса внутри черты :)


1

Этот признак такой же, как класс, который мы можем использовать для множественного наследования, а также для повторного использования кода.

Мы можем использовать черты внутри класса, а также мы можем использовать несколько черт в одном классе с помощью ключевого слова use.

Интерфейс использует для повторного использования кода так же, как черта

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

http://php.net/manual/en/language.oop5.traits.php http://php.net/manual/en/language.oop5.interfaces.php


1

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

Черта - это, по сути, способ «копировать и вставлять» код между классами.

Попробуйте прочитать эту статью. Что такое черты PHP?


0

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

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