Mixins против композиции в скале - PullRequest
76 голосов
/ 06 августа 2010

В мире Java (точнее, если у вас нет множественного наследования / миксинов), практическое правило довольно простое: «Подарите композицию объекта наследованию класса».

Я хотел бы знать, если / как это изменилось, если вы также рассматриваете миксины, особенно в Scala?
Считаются ли миксины способом множественного наследования или составления классов?
Существует ли также рекомендация «Композиция объекта Favor поверх композиции класса» (или наоборот)?

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

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

Я знаю, что короткий ответ "Это зависит", но, вероятно, есть некоторые типичные ситуации, когда то или иное лучше.

Некоторые примеры руководств, которые я мог придумать до сих пор (при условии, что у меня есть две черты A и B, и A хочет использовать некоторые методы из B):

  • Если вы хотите расширить API A с помощью методов из B, тогда mixins, иначе состав. Но это не помогает, если создаваемый мной класс / экземпляр не является частью общедоступного API.
  • Если вы хотите использовать некоторые паттерны, которые нуждаются в миксинах (например, Stackable Trait Pattern ), тогда это простое решение.
  • Если у вас есть циклические зависимости, то могут помочь миксины с типами. (Я пытаюсь избежать этой ситуации, но это не всегда легко)
  • Если вам нужны динамические решения во время выполнения, как сделать композицию, тогда составьте объект.

Во многих случаях миксины кажутся более простыми (и / или менее многословными), но я вполне уверен, что у них также есть некоторые подводные камни, такие как «класс Бога» и другие, описанные в двух статьях artima: часть 1 , part 2 (Кстати, мне кажется, что большинство других проблем не имеют отношения / не столь серьезны для scala).

У вас есть еще подобные подсказки?

Ответы [ 2 ]

39 голосов
/ 06 августа 2010

Многие проблемы, которые возникают у людей с дополнительными модулями, могут быть предотвращены в 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 

Эта конструкция может рекомендовать несколько вещей.Во-первых, это предотвращает взрыв классов, поскольку необходимы различные комбинации функциональных возможностей.Во-вторых, он позволяет легко тестировать, так как можно создавать и смешивать конкретные черты «ничего не делать», похожие на фиктивные объекты.Наконец, мы полностью скрыли используемую черту блокировки и даже эту блокировку от потребителей нашего сервиса.

Поскольку мы преодолели большинство заявленных недостатков встраивания, мы все равно остаемся в компромиссе между смешиванием и составом.Для себя я обычно принимаю решение, основываясь на том, будет ли гипотетический объект делегата полностью инкапсулирован содержащим его объектом, или он может потенциально использоваться совместно и иметь собственный жизненный цикл.Блокировка является хорошим примером полностью инкапсулированных делегатов.Если ваш класс использует объект блокировки для управления одновременным доступом к своему внутреннему состоянию, эта блокировка полностью контролируется содержащим объектом, и ни он, ни его операции не объявляются как часть открытого интерфейса класса.Для полностью инкапсулированной функциональности, подобной этой, я использую дополнения.Для чего-то общего, например источника данных, используйте состав.

10 голосов
/ 06 августа 2010

Другие различия, которые вы не упомянули:

  • Классы черт не существуют независимо:

( Программирование Scala )

Если вы обнаружите, что определенная черта чаще всего используется как родительская для других классов, так что дочерние классы ведут себя как родительская черта, то вместо этого рассмотрите определение черты как класса, чтобы сделать это логичнымотношения более ясны.
(Мы сказали, что ведет себя как , а не - это , потому что первое - это более точное определение наследования, основанное на принципе подстановки Лискова - см. [Martin2003], например.)

[Martin2003]: Роберт К. Мартин, Agile Software Development: принципы, шаблоны и практики, Prentice-Hall, 2003

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

Отсюда и совет , по-прежнему от Scala :

Избегайте конкретных полей в чертах, которые нельзя инициализировать для подходящих значений по умолчанию.
Вместо этого используйте абстрактные поля или преобразуйте черту в класс с конструктором .
Конечно, черты без состояния не имеют проблем с инициализацией.

Это общий принципиз хорошего объектно-ориентированного проектирования, что экземпляр всегда должен находиться в известном допустимом состоянии, начиная с момента завершения процесса построения .

Эта последняя часть, касающаяся начальное состояние объекта, часто помогало выбирать между классом (и составом класса) и признаком (и миксинами) для данного понятия.

...