18

There are three common ways, AFAIK, to implement reusability when it comes to OOP

  1. Inheritance : usually to represent is-a relationship (a duck is-a bird)
  2. Composition: usually to represent has-a relationship (a car has-a engine)
  3. Traits (e.g. the trait keyword in PHP) : ...not really sure about this

While it looks to me that traits can implement both has-a and is-a relationships, I am not really sure what kind of modelling it was intended for. What kind of situations were traits designed for?

4

1 Answer 1

15

Traits are another way to do composition. Think of them as a way to compose all the parts of a class at compile time (or JIT compile time), assembling the concrete implementations of the parts you will need.

Basically, you want to use traits when you find yourself making classes with different combinations of features. This situation comes up most often for people writing flexible libraries for others to consume. For example, here's the declaration of a unit test class I wrote recently using ScalaTest:

class TestMyClass
  extends WordSpecLike
  with Matchers
  with MyCustomTrait
  with BeforeAndAfterAll
  with BeforeAndAfterEach
  with ScalaFutures

Unit test frameworks have a ton of different configuration options, and every team has different preferences about how they want to do things. By putting the options into traits (which are mixed in using with in Scala), ScalaTest can offer all those options without having to create class names like WordSpecLikeWithMatchersAndFutures, or a ton of runtime boolean flags like WordSpecLike(enableFutures, enableMatchers, ...). This makes it easy to follow the Open/closed principle. You can add new features, and new combinations of features, simply by adding a new trait. It also makes it easier to follow the Interface Segregation Principle, because you can easily put functions not universally needed into a trait.

Traits are also a good way to put common code into several classes that do not make sense to share an inheritance hierarchy. Inheritance is a very tightly-coupled relationship, and you shouldn't pay that cost if you can help it. Traits are a much more loosely-coupled relationship. In my example above, I used MyCustomTrait to easily share a mock database implementation between several otherwise unrelated test classes.

Dependency injection achieves many of the same goals, but at runtime based on user input instead of at compile time based on programmer input. Traits are also intended more for dependencies that are semantically part of the same class. You're sort of assembling the parts of one class rather than making calls out to other classes with other responsibilities.

Dependency injection frameworks achieve many of the same goals at compile time based on programmer input, but are largely a workaround for programming languages without proper trait support. Traits bring these dependencies into the realm of the compiler's type checker, with cleaner syntax, with a simpler build process, that makes a more clear distinction between compile-time and runtime dependencies.

5
  • Hi, this is a great answer. May I ask how do decide when to use traits, and when to use dependency injection. which seems to be achieve the same.
    – Extrakun
    Mar 22, 2016 at 5:27
  • Astute observation. See my edit. Mar 22, 2016 at 16:11
  • 1
    Thanks for writing this up. I'm curious about your answer about traits being like composition @KarlBielefeldt. Your class can be used wherever that trait is needed, and you would still suffer from the same fragility as with a regular interface. So it seems closer to subtyping and inheritance, no? I'm also unsure how it's similar to DI...if you have various classes defined that extend traits, then aren't those concrete dependencies defined wherever in the hierarchy that class is defined, rather than at an top / entry point of the code? Thanks!
    – allstar
    Apr 17, 2020 at 21:54
  • Traits can be used just like interfaces, for their inheritance properties, but that's not what makes them unique. The with operator is what differentiates them, and with is a composition operation. And yes, traits replace DI without needing to be defined at the entry point of the code. That's one of the things that makes them preferable in my opinion. Apr 17, 2020 at 23:50
  • I use traits to satisfy the requirements of an interface, and to reduce code duplication. I test for implementation of an interface, rather than use of a trait at runtime, though.
    – J.D. Pace
    Jun 25, 2022 at 17:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.