Черты и интерфейсы
Я пытался изучить PHP в последнее время, и я обнаружил, что зацикливаюсь на чертах. Я понимаю концепцию повторного использования горизонтального кода и не хочу обязательно наследовать от абстрактного класса. Что я не понимаю, так это то, что является решающим отличием между использованием черт и интерфейсов?
Я пробовал искать приличный пост в блоге или статью, объясняющую, когда использовать тот или иной, но примеры, которые я нашел до сих пор, кажутся такими похожими идентичный.
может ли кто-нибудь там поделиться своим мнением/взглядом на это?
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 amixin
, и фактически представляет собой набор методов расширения, которые могут быть добавлены в любой класс с помощью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
extendedIBaseInterface
, массивIBaseInterface[] bases
может содержать экземплярыMyClass
. Такой полиморфной конструкции не существует доступный сtrait
- потому что atrait
это по существу просто код, который копируется для удобства программиста в каждый класс, который его использует.приоритет:
как описано в руководстве:
наследуемый член базового класса переопределяется членом, вставленным признаком. Порядок приоритета заключается в том, что члены текущего класса переопределяют методы признаков, которые в свою очередь переопределяют унаследованные методы.
Итак - рассмотрим следующий сценарий:
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, выше, происходит следующее:
- The
Interface
IBase
требуется функция без параметров называетсяSomeMethod()
должны быть предоставлены.- базовый класс
BaseClass
обеспечивает реализацию этого метода-удовлетворение потребности.- The
trait
myTrait
предоставляет функцию без параметров называетсяSomeMethod()
как ну, который берет верх наBaseClass
версия- The
class
MyClass
предоставляет свою собственную версиюSomeMethod()
-который берет верх наtrait
-версия.вывод
- An
Interface
не может обеспечить реализацию по умолчанию метода, в то время какtrait
может.- An
Interface
это полиморфных, унаследовала построить-пока atrait
нет.- несколько
Interface
s может использоваться в том же классе, и так может несколько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 общих решения:
- определите базовый класс с кодом события pub / sub, а затем классы, которые хотят предложить события, могут расширить его, чтобы получить возможности.
- определите класс с кодом события pub / sub, а затем другие классы, которые хотят предложить события, могут использовать его через композицию, определяя свои собственные методы для оберните составленный объект, проксируя вызовы метода к нему.
- определите признак с кодом события 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