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