Оголошення про державну службу:
Я хочу зазначити напис, що я вважаю, що риси майже завжди кодового запаху, і їх слід уникати на користь композиції. На мою думку, одинакове успадкування часто зловживається до того, що воно є антидіаграмою, і багатократне успадкування лише посилює цю проблему. У більшості випадків вам набагато краще подавати перевагу композиції над спадщиною (будь то поодинокі чи множинні). Якщо ви все ще цікавитесь рисами та їхнім відношенням до інтерфейсів, читайте далі ...
Почнемо з цього:
Об'єктно-орієнтоване програмування (ООП) може бути важкою парадигмою. Тільки тому, що ви використовуєте класи, не означає, що ваш код орієнтований на об'єкти (OO).
Щоб написати код OO, вам потрібно зрозуміти, що OOP дійсно стосується можливостей ваших об'єктів. Ви повинні думати про заняття з точки зору того, що вони можуть зробити, а не те, що вони насправді роблять . Це суворо на відміну від традиційного процедурного програмування, де акцент робиться на створенні трохи коду "зроби щось".
Якщо код OOP стосується планування та проектування, інтерфейс - це план, а об'єктом є повністю побудований будинок. Тим часом риси - це просто спосіб допомогти побудувати будинок, викладений планом (інтерфейсом).
Інтерфейси
Отже, навіщо нам використовувати інтерфейси? Простіше кажучи, інтерфейси роблять наш код менш крихким. Якщо ви сумніваєтесь у цьому твердженні, запитайте всіх, кого змусили зберігати застарілий код, який не був написаний проти інтерфейсів.
Інтерфейс - це договір між програмістом та його кодом. В інтерфейсі написано: "Поки ти граєш за моїми правилами, ти можеш реалізовувати мене як завгодно, і я обіцяю, що я не порушу твій інший код".
Отже, як приклад, розгляньте сценарій у реальному світі (без автомобілів чи віджетів):
Ви хочете застосувати систему кешування для веб-програми, щоб зменшити навантаження на сервер
Ви починаєте з написання класу для кешування відповідей на запит за допомогою 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
із обговорення інтерфейсу використовують один і той же метод, щоб визначити, чи є запис кеша несвіжим і його слід видалити (очевидно, це не так у реальному житті, але йдіть з ним). Ви можете написати ознаку і дозволити обом класам використовувати її для загальної інтерфейсної вимоги.
Останнє слово обережності: будьте обережні, щоб не перебирати за борт риси. Часто риси використовуються як милиця для поганого дизайну, коли вистачить унікальних реалізацій класу. Ви повинні обмежити риси виконання вимог інтерфейсу для найкращого дизайну коду.