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