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

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

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

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

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

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

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

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

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

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

2 ответов


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


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

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

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

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

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

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

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

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

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

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