Объявление о государственной службе:
Я хочу заявить, что черты почти всегда являются запахом кода, и их следует избегать в пользу композиции. По моему мнению, одиночное наследование часто злоупотребляет до такой степени, что оно является анти-паттерном, а множественное наследование только усугубляет эту проблему. В большинстве случаев вам будет гораздо лучше обслуживать, отдавая предпочтение композиции перед наследованием (будь то одно или несколько). Если вам все еще интересны черты и их связь с интерфейсами, читайте дальше ...
Давайте начнем с того, что скажем:
Объектно-ориентированное программирование (ООП) может быть трудной для понимания парадигмой.
То, что вы используете классы, не означает, что ваш код
Объектно-ориентированный (ОО).
Чтобы написать ОО-код, вы должны понимать, что ООП действительно зависит от возможностей ваших объектов. Вы должны думать о классах с точки зрения того, что они могут сделать вместо того, что они на самом деле делают . Это резко контрастирует с традиционным процедурным программированием, в котором основное внимание уделяется тому, чтобы часть кода «что-то делала».
Если код ООП касается планирования и проектирования, интерфейс - это проект, а объект - это полностью построенный дом. Между тем, черты характера - это просто способ помочь построить дом, спроектированный планом (интерфейс).
Интерфейсы
Итак, почему мы должны использовать интерфейсы? Проще говоря, интерфейсы делают наш код менее хрупким. Если вы сомневаетесь в этом утверждении, спросите любого, кто был вынужден поддерживать устаревший код, который не был написан для интерфейсов.
Интерфейс - это контракт между программистом и его / ее кодом. Интерфейс говорит: «Пока вы играете по моим правилам, вы можете реализовывать меня так, как вам нравится, и я обещаю, что не нарушу ваш другой код».
В качестве примера рассмотрим сценарий реального мира (без машин и виджетов):
Вы хотите внедрить систему кеширования для вырезания веб-приложения
при загрузке сервера
Вы начинаете с написания класса для кэширования ответов на запросы с использованием 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
из обсуждения интерфейса используют один и тот же метод, чтобы определить, является ли запись в кэше устаревшей и ее следует удалить (очевидно, это не так в реальной жизни, но иди с этим).Вы можете написать черту и позволить обоим классам использовать ее для общего требования к интерфейсу.
Последнее слово предостережения: будьте осторожны, чтобы не переборщить с чертами.Часто черты используются в качестве опоры для плохого дизайна, когда реализации уникального класса будет достаточно.Вы должны ограничивать черты в соответствии с требованиями интерфейса для лучшего дизайна кода.