Подтвердить что ты не робот

Микшины против композиции в scala

В java-мире (точнее, если у вас нет множественного наследования /mixins ), эмпирическое правило довольно просто: "Использовать состав объектов над наследованием класса".

Я хотел бы знать, если/как это изменилось, если вы также рассматриваете mixins, особенно в scala?
Смешины считаются способом множественного наследования, или более классным составом?
Существует ли также "Опорная структура объекта над классом" (или наоборот)?

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

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

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

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

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

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

Есть ли у вас больше таких советов?

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

  • Классы классов не имеют никакого независимого существования:

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

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

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

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

Следовательно, совет, еще от программирования Scala:

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

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

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