Mixins vs composition in scala

后端 未结 2 1402
暗喜
暗喜 2021-01-29 20:49

In java world (more precisely if you have no multiple inheritance/mixins) the rule of thumb is quite simple: \"Favor object composition over class inheritance\".

I\'d

相关标签:
2条回答
  • 2021-01-29 21:13

    Other differences you haven't mentioned:

    • Trait classes do not have any independent existence:

    (Programming Scala)

    If you find that a particular trait is used most often as a parent of other classes, so that the child classes behave as the parent trait, then consider defining the trait as a class instead, to make this logical relationship more clear.
    (We said behaves as, rather than is a, because the former is the more precise definition of inheritance, based on the Liskov Substitution Principle - see [Martin2003], for example.)

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

    • mixins (trait) have no constructor parameters.

    Hence the advice, still from Programming Scala:

    Avoid concrete fields in traits that can’t be initialized to suitable default values.
    Use abstract fields instead or convert the trait to a class with a constructor.
    Of course, stateless traits don’t have any issues with initialization.

    It’s a general principle of good object-oriented design that an instance should always be in a known valid state, starting from the moment the construction process finishes.

    That last part, regarding the initial state of an object, has often helped decide between class (and class composition) and trait (and mixins) for a given concept.

    0 讨论(0)
  • 2021-01-29 21:21

    A lot of the problems that people have with mix-ins can be averted in Scala if you only mix-in abstract traits into your class definitions, and then mix in the corresponding concrete traits at object instantiation time. For instance

    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 construct has several things to recommend it. First, it prevents there from being an explosion of classes as different combinations of trait functionalities are needed. Second, it allows for easy testing, as one can create and mix-in "do-nothing" concrete traits, similar to mock objects. Finally, we've completely hidden the locking trait used, and even that locking is going on, from consumers of our service.

    Since we've gotten past most of the claimed drawbacks of mix-ins, we're still left with a tradeoff between mix-in and composition. For myself, I normally make the decision based on whether a hypothetical delegate object would be entirely encapsulated by the containing object, or whether it could potentially be shared and have a lifecycle of its own. Locking provides a good example of entirely encapsulated delegates. If your class uses a lock object to manage concurrent access to its internal state, that lock is entirely controlled by the containing object, and neither it nor its operations are advertised as part of the class's public interface. For entirely encapsulated functionality like this, I go with mix-ins. For something shared, like a datasource, use composition.

    0 讨论(0)
提交回复
热议问题