Черты и интерфейсы


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

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

может ли кто-нибудь там поделиться своим мнением/взглядом на это?

13 271

13 ответов:

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

когда черта use ' D реализации методов приходят вместе тоже-что не происходит в Interface.

это большая разница.

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

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

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

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


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

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

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

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

интерфейсы

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

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

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

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

вы начинаете с написания класса для кэширования ответов на запросы с помощью 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 из обсуждения интерфейса используйте тот же метод, чтобы определить, является ли запись кэша устаревшей и должна быть удалена (очевидно, это это не так в реальной жизни, но идти с ним). Вы можете написать признак и разрешить обоим классам использовать его для общего требования к интерфейсу.

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

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

с руководство 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 extended IBaseInterface, массив IBaseInterface[] bases может содержать экземпляры MyClass. Такой полиморфной конструкции не существует доступный с trait - потому что a 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. The InterfaceIBase требуется функция без параметров называется SomeMethod() должны быть предоставлены.
  2. базовый класс BaseClass обеспечивает реализацию этого метода-удовлетворение потребности.
  3. The traitmyTrait предоставляет функцию без параметров называется SomeMethod() как ну, который берет верх на BaseClassверсия
  4. The classMyClass предоставляет свою собственную версию SomeMethod() -который берет верх на trait-версия.

вывод

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

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

например:

trait ToolKit
{
    public $errors = array();

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

вы можете иметь и использовать этот "метод ошибок" в любом классе использует это черта характера.

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. Это не бывает с чертами характера.

это совсем другое!

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

черт

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, он не может иметь переменных.

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

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

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

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

здесь are функции теперь в PHP, которые позволят вам получить список всех признаков, используемых классом, но наследование признаков означает, что вам нужно будет выполнить рекурсивные проверки, чтобы надежно проверить, имеет ли класс в какой-то момент определенную черту (есть пример кода на страницах PHP doco). Но да, это, конечно, не так просто и чисто, как 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, чтобы определить, является ли конкретный объект двери ключом или нет, вы знаете вы получите последовательный набор методов и т. д., и весь код находится в одном месте во всех классах, которые используют KeyedTrait.

черт просто повторное использование кода.

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

для ссылка- http://www.php.net/manual/en/language.oop5.traits.php

вы можете рассматривать черту как автоматическую "копипаст" кода, в основном.

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

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

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

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

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

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

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

  1. определите базовый класс с кодом события pub / sub, а затем классы, которые хотят предложить события, могут расширить его, чтобы получить возможности.
  2. определите класс с кодом события pub / sub, а затем другие классы, которые хотят предложить события, могут использовать его через композицию, определяя свои собственные методы для оберните составленный объект, проксируя вызовы метода к нему.
  3. определите признак с кодом события pub / sub, а затем другие классы, которые хотят предложить события, могут 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() метод как protected. Итак,единственное отличие, которое вам нужно посмотреть, это реализация Auction класс.

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

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

признак-это, по сути, способ "копировать и вставлять" код между классами.

попробуйте прочитать эту статью

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

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

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

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

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

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

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

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