简体   繁体   中英

How to determine to use trait to 'with' or class to 'inject'?

I'm puzzled to choose a trait or class when writing scala code.

At first, I have a controller which with several traits:

class MyController extends Controller 
                   with TransactionSupport 
                   with JsonConverterSupport 
                   with LoggerSupport

In these traits, I defined some methods and fields which can be used in MyController directly.

But my friend says: when you extends or with a trait, it should be a that trait.

Look at the MyController , it is a Controller , but it isn't a TransactionSupport , not a JsonConverterSupport , not a LoggerSupport , so it should not with them.

So the code becomes:

class MyController(tranSupport: TransactionSupport, 
                   jsonConverter: JsonConverterSupport, 
                   loggerSupport: LoggerSupport) extends Controller

But I don't feel good about this code, it just seems strange.

I see trait s used heavily in scala code, when should I use it or use classes to inject?

I'll refer you to Interfaces should be Adjectives . Though some traits may play the part of a class (and, therefore, be nouns and respect the "is-a" relationship), when used as mixins they'll tend to play the part of interfaces.

As an "adjective", the trait will add a qualifying property to whatever they are extending. For example, they may be Comparable or Serializable .

It can be a bit hard to find an adjective to fit -- what adjective would you use for LoggerSupport ? -- so don't feel overly constrained by that. Just be aware that it is completely wrong to thing of traits as necessarily an "is-a" relationship.

I would try to avoid using traits to replace "has-a" relationships, though.

My opinion is that it doesn't have to be it. Mixing-in is a different concept than inheritance. Even though syntactically it is the same, it doesn't mean the same. Typical use case for mixing-in is logging just like you wrote. It doesn't mean that if your service class mixes-in a Logging trait that it is a logger. It's just a yet another way how to compose functionality into working objects.

Odersky proposes that if you are not sure and you can, use trait s because they are more flexible. You can change trait to class in the future if you need.

Sometime when I feel that mixing-in trait doesn't look good, I use module pattern like this:

trait JsonConverterModule {

    protected def jsonConverter: JsonConverter

    protected trait JsonConverter {
      def convert(in: Json): Json
    }
  }

class MyController extends Controller with JsonConverterModule {
   private doSmth = jsonConverter.convert(...)
}

MyController in this case looks more like a Controller, and all Json-related stuff is hidden from MyController 'client'

Your first example with traits is the "cake pattern" and your second example is "constructor injection". Both are perfectly valid ways to do dependency injection in Scala. The cake pattern is powerful, you can inject type members, the different traits can easily talk to each other (we don't have to create separate objects and pass them to each other object, often requiring setter injection rather than simple constructor injection), etc. However, the type has to be realized at compile-time, and a separate class must be realized for every combination of traits. Constructor injection lets you build your object at run-time and scales better for a large number of combinations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM