Як я використовую трейты

Нещодавно кілька людей просили мене розповісти про використання трейтов у новому проекті, який я пишу. Приблизно в той же час Рафаель Домс показав мені його нову йдеться про складних когнітивних процесах, які ми не помічаємо. Так як наш мозок — це великий мішок, перемішуючий все, в результаті вийшов пост, який намагається охопити і те як я використовують трейты, і я вирішую де вони потрібні.

Вплив vs Абстракція

Перше, що ви повинні зробити — піти почитати пост «Abstraction or Leverage» від Майкла Найгарда. Це відмінна стаття.

Якщо ж у вас мало часу, основна суть посту полягає в тому, що частини коду (функції, класи, методи і т. д.) можуть призначатися або абстракції, або для впливу. Різниця в:

  • Абстракція містить високорівневий концептуальний код, що дозволяє лаконічніше працювати з ним іншим кодом.
  • Вплив містить код, зміни в якому впливають лише на певну частину.

Загальною абстракцією буде патерн Репозиторій: ви не знаєте як об'єкт зберігається або де, вам все одно. Деталі лежать поза концепції Сховища.

Загальним впливом буде щось на кшталт базового класу контролера у вашому фреймворку. Він не приховує реалізацію, просто додає деякі класні штуки, що полегшують роботу.

У вищезгаданому повідомленні йдеться, що як абстракції, так і впливу хороші. Але абстракція — трохи краще, тому що вона завжди дає вам можливість впливати, а вплив не дає вам абстракції. Тим не менше, я хотів би додати, що гарна абстракція є більш трудозатратной у створенні і не на всіх рівнях можлива. Так що це є компроміс.

Як це пов'язано з трейтами?

Деякі можливості мови краще, ніж інші, у створенні якого впливу, або абстракції. Інтерфейси, наприклад, відмінно допомагають нам будувати і застосовувати абстракції.

Успадкування, з іншого боку, чудово в наданні впливу. Воно дозволяє нам змінити частини батьківського коду без необхідності копіювати або витягувати кожен метод, використовувати код класу (але не обов'язково абстрактного) кілька разів. Так, відповідаючи на початковий питання, коли ж я можу використовувати трейты?

Я використовую трейты, коли я хочу створити вплив, не абстракцію.

Іноді.

Іноді?

Бенджамін Эберлей висловив хороший аргумент, що трейты мають в основному ті ж проблеми, що і статичний доступ. Ви не можете замінити або замінити їх, вони відверто погано піддаються тестуванню.

Але все ж статичні методи корисні. Якщо у вас одна функція без стану і ви не хочете замінити її на іншу реалізацію, то немає нічого поганого в тому, щоб зробити її статичною. Іменовані конструктори (ви ж рідко хочете саме порожній об'єкт) або отримання масиву/результату математичних операцій з добре визначеними введенням/виведенням, без стану, детерміновані: все це вам цікаво. Статичний стан, а не методи, ось реальне зло.

Трейты мають приблизно ті ж обмеження, плюс вони можуть бути використанні тільки усередині класу. Вони більш глобальні, ніж об'єкт.

Це дає трейтам додаткову особливість: вони можуть працювати (читати і писати) з внутрішнім станом класу, в який підмішані. У деяких випадках це робить їх більш придатними ніж статичні методи.

Наприклад, я часто використовую генерацію доменних подій в сутності:

trait GeneratesDomainEvents
{
private $events = [];

protected function raise(DomainEvent $event)
{
$this->events[] = $event;
}

public function releaseEvents()
{
$pendingEvents = $this->events;
$this->events = [];
return $pendingEvents;
}
}

Ми можемо зробити рефакторинг і перетворити цей код на абстракцію, але це все одно буде хорошим прикладом того, як трейты можуть працювати з локальним станом об'єкта на відміну від статичних методів. Ми не хочемо працювати з масивом подій наосліп або переміщати його з об'єкта. Можливо, ми не хочемо додавати ще одну абстракцію всередину моделі, і нам, звичайно ж, не хочеться копіпаст цей шаблонний код скрізь. І тут знову нам допоможуть трейты.

Іншими практичними прикладами можуть служити настроюється логування функцій, дамп декількох властивостей відразу або загальна ітераційна/пошукова логіка. Ми могли б вирішити всі ці завдання батьківським класом, але поговоримо про це трохи пізніше.

Отже, трейты є гарною заміною в подібних випадках, але це не означає, що статичні методи марні. Насправді, я все ж віддаю перевагу використовувати статичні методи у випадках, коли не потрібно змінювати внутрішній стан об'єкта, оскільки це завжди безпечніше. Статичні методи також набагато зручніше в тестуванні, не вимагають mock класу.

Створення тверджень є хорошим прикладом тих випадків, де я віддаю перевагу статичні методи, незважаючи на те, що їх зазвичай можна помістити в трейты. Я знаходжу, що
Assertion::positiveNumber($int)
дає мені вищезазначені переваги і мені легше зрозуміти що робить викликається клас.

Якщо у вас є подібні методи тверджень, завжди виникає спокуса перетворити їх в трейты, але подібний код починає тхнути». Можливо метод вимагає кілька параметрів і ви втомилися їх передавати. Можливо перевірка
$this->foo
вимагає значення
$this->bar
. У будь-якому з цих випадків рефакторинг класу буде кращою альтернативою. Пам'ятайте, завжди краще, якщо вплив поступається місце абстракції.

Так що заявляю: Я використовую трейты, коли я хочу впливу, яким потрібен доступ до внутрішнього стану об'єкта.

Батьківські класи

Все що ми перерахували також можна реалізувати через успадкування. В
EventGeneratingEntity
можливо такий підхід був би навіть краще, оскільки масив подій дійсно буде індивідуальним. Однак, трейты дають можливість множинного спадкування замість одного базового класу. Крім набору функцій, є ще хороші аргументи за такий підхід?

При інших рівних, я б орієнтувався на правило кшталт «Є-A Має проти-A». Звичайно, це не точне правило, тому що трейты не є композицією, але розумний орієнтир.

Іншими словами, батьківські класи потрібно використовувати для функцій, які притаманне якомусь об'єкту. Батьківські класи добре передають іншим розробникам сенс коду: «Працівник — це людина». Якщо нам необхідно вплив, це не означає, що код не повинен бути комунікативним.

Для іншої непрофільної функціональності об'єкта (логування, події і т. д.) трейты є підходящим інструментом. Вони не визначають характер класу, це допоміжні функції, або ще краще — деталь реалізації. Все що ви отримаєте від трейта повинно перебувати на службі у головної мети об'єкта, трейты не повинні стати важливою частиною функціональності.

Так що, в разі генерації подій, я все-таки віддам перевагу трейт, тому що створення подій — це допоміжний функціонал.

Інтерфейси

Я рідко (якщо взагалі) розширюю клас або створюю трейт без супутнього створення інтерфейсу.

Якщо ви будете слідувати цьому правилу, то виявите, що трейты добре доповнюють принципи поділу інтерфейсів. Легко визначити інтерфейс для додаткової функціональності і зробити просту реалізацію в трейте за замовчуванням.

Це дозволяє цільовим класу реалізовувати свою власну версію інтерфейсу або використовувати трейт за замовчуванням для неважливих випадків. Якщо ваш вибір — це шаблони і форсування бідної абстракції, трейты можуть бути потужним союзником.

І ще, якщо у вас тільки один реалізує інтерфейс клас у коді і ви не плануєте змінювати, то имплементируйте функціонал прямо в ньому, не турбуйтеся про трейтах. Таким чином ви зробите ваш код більш читабельним і підтримуваним.

Коли я не використовую трейты

Чесно кажучи, я не використовую трейты досить часто, можливо раз на кілька місяців я створюю трейт при постійній роботі над проектами. Вся ця евристика, яку я обрисовывал (вплив, що вимагає доступу до внутрішнього стану) є вкрай нишової. Якщо ви використовуєте їх занадто часто, можливо вам потрібно зробити крок назад і переглянути свій стиль програмування. Є хороший шанс, що тисячі класів тільки й чекають, щоб бути реалізованими.

Є кілька місць, де я не люблю використовувати трейты з-за стильових переваг:

  • Якщо код, за яким ви працюєте — це просто пара геттеров і сеттерів, я б не став заморочуватися. IDE можуть бути тут гарним козирем, а додавання трейта залишить після себе тільки зниження доступності.
  • Не використовуйте трейты для впровадження залежностей. Не стільки із-за особливостей трейтов, скільки з-за особливостей сеттерів залежностей, я проти цього.
  • Мені не подобається використання трейтов у великих загальнодоступних API або великих шматках функціональності. Пропустіть етап впливу і переходьте безпосередньо до абстракції.
І нарешті, слід пам'ятати, що трейты не припускають абстракцію і вони не є композицією, але все одно мають право зайняти місце серед ваших інструментів. Вони корисні для надання впливу за замовчуванням при більш дрібних реалізацію або дублювання коду. Завжди будьте готові реорганізувати їх для кращої абстракції, як тільки відчуєте ознаки коду «із запашком».

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.