Міксіни проти складу в Scala


78

У світі Java (точніше, якщо у вас немає багаторазового успадкування / мікшину), емпіричне правило досить просте: "Надавайте перевагу композиції об'єкта над успадкуванням класу".

Я хотів би знати, чи / як це змінюється, якщо ви також розглядаєте міксини, особливо в Scala?
Чи вважаються міксини способом багаторазового успадкування чи більшим складом класу?
Чи існує також вказівка ​​"Надати перевагу композиції об'єкта порівняно зі складом класу" (або навпаки)?

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

  • видимість - з мікшинами все стає частиною публічного API, чого не можна сказати про композицію.
  • багатослівність - у більшості випадків міксини менш багатослівні і дещо простіші у використанні, але це не завжди так (наприклад, якщо ви також використовуєте власні типи в складних ієрархіях)

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

Деякі приклади вказівок, які я міг би придумати до цього часу (припускаючи, що я маю дві риси A і B, і A хоче використовувати деякі методи з B):

  • Якщо ви хочете розширити API A методами з B, то змішуйте, інакше склад. Але це не допомагає, якщо клас / екземпляр, який я створюю, не є частиною загальнодоступного API.
  • Якщо ви хочете використовувати деякі шаблони, для яких потрібні міксини (наприклад, шаблон, що складається з ознак ), це просте рішення.
  • Якщо у вас є кругові залежності, тоді можуть допомогти поєднання з власними типами. (Я намагаюся уникати цієї ситуації, але це не завжди легко)
  • Якщо вам потрібні певні динамічні рішення, як виконувати композицію, а потім композицію об’єкта.

У багатьох випадках міксини здаються простішими (і / або менш багатослівними), але я впевнений, що вони також мають деякі підводні камені, такі як "клас Бога" та інші, описані у двох статтях artima: частина 1 , частина 2 (BTW it Мені здається, що більшість інших проблем не є актуальними / не такими серйозними для Scala).

У вас є більше таких підказок?

Відповіді:


41

Багато проблем, які виникають у людей із змішаннями, можна уникнути в Scala, якщо ви лише змішаєте абстрактні риси у визначенні класу, а потім змішаєте відповідні конкретні риси під час створення об’єкта. Наприклад

trait Locking{
   // abstract locking trait, many possible definitions
   protected def lock(body: =>A):A
}

class MyService{
   this:Locking =>
}

//For this time, we'll use a java.util.concurrent lock
val myService:MyService = new MyService with JDK15Locking 

Ця конструкція має кілька речей, які рекомендують. По-перше, це запобігає вибуху класів, оскільки потрібні різні комбінації функціональних ознак. По-друге, це дозволяє легко тестувати, оскільки можна створювати та змішувати конкретні риси "нічого не робити", подібні до макетних об'єктів. Нарешті, ми повністю приховали використану ознаку блокування, і навіть це блокування триває від споживачів нашої послуги.

Оскільки ми подолали більшість заявлених недоліків змішань, нам все ще залишається компроміс між змішуванням та композицією. Для себе я, як правило, приймаю рішення, виходячи з того, чи буде гіпотетичний об'єкт-делегат повністю інкапсульований вміщуючим об'єктом, чи він потенційно може бути спільним та мати власний життєвий цикл. Блокування є хорошим прикладом повністю інкапсульованих делегатів. Якщо ваш клас використовує об’єкт блокування для керування паралельним доступом до його внутрішнього стану, це блокування повністю контролюється вміщуючим об’єктом, і ні він, ні його дії не рекламуються як частина загальнодоступного інтерфейсу класу. Для повністю інкапсульованих функціональних можливостей, подібних цій, я використовую комбінації. Для чогось спільного, наприклад джерела даних, використовуйте склад.


Що означає конструкція this => Logging? Він не компілюється.
HRJ

3
Це анотація самотипу, про яку я завжди забуваю синтаксис. Відредаговано. У цьому випадку це означає, що будь-який об'єкт, що поширює MyService, повинен також розширити Locking
Dave Griffith

11

Інші відмінності, про які ви не згадали:

  • Класи ознак не мають самостійного існування:

( Програмування Scala )

Якщо ви виявите, що певна ознака використовується найчастіше як батьківська для інших класів, так що дочірні класи поводяться як батьківська ознака, тоді подумайте про те, щоб визначити ознаку як клас, щоб зробити цей логічний взаємозв'язок більш зрозумілим.
(Ми сказали, що поводиться як , а не як , оскільки перше - це більш точне визначення спадщини, засноване на Принципі заміщення Ліскова - див., Наприклад, [Martin2003]).

[Martin2003]: Роберт К. Мартін, Agile Розробка програмного забезпечення: Принципи, зразки та практики, Prentice-Hall, 2003

  • mixins ( trait) не мають параметрів конструктора.

Звідси порада, все ще від програмування Scala :

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

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

Ця остання частина, що стосується початкового стану об’єкта, часто допомагає визначитися між класом (і складом класу) та ознакою (та мікшинами) для даного поняття.


Дякую за вашу відповідь, але я думаю, що це більше стосується ознак проти класів. Мені трохи комфортніше в цій темі :-) Можливо, я був недостатньо точним, коли вживав термін "склад класу". Те, що я мав на увазі, описується в статті «Шаблон шаблону ознак, що складаються»: «Цей шаблон схожий за структурою на шаблон декоратора, за винятком того, що він передбачає декорування з метою композиції класу замість композиції об’єкта», хоча і поєднує класи та риси.
Шандор Муракозі,

@ Сандор: Я розумію. Я розглядав основну частину Вашого запитання, оскільки мені ще не до кінця приємна ця відмінність, але незабаром Ви повинні отримати точніші відповіді.
VonC

" mixins ( trait) не мають параметрів конструктора. " Цікаво, що Dotty (щоб стати Scala 3) підтримує параметри ознак . Як результат, еліптичне правило програмування Scala, яке ви згадали, може еволюціонувати.
jub0bs

1
@Jubobs Дякую. Через 8 років я не здивований, що це правило може розвинутися.
VonC
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.