Миксины против состава в scala


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

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

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

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

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

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

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

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

есть ли у вас больше намеков, как эти?

2 70

2 ответа:

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

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

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

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

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

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

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

[Martin2003]: Robert C. Martin, Agile Software Development: Principles, Patterns, and Practices, Prentice-Hall, 2003

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

отсюда совет, все еще от Программирования Scala:

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

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

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