What are Traits?
Traits are basically just a collection of methods that let you share an implementation across inheritance hierarchies. It is a class like structure that cannot be instantiated. Think of it as includes for classes.
The Good
Traits are defined as “horizontal inheritance“. Therefore it’s good to prevent the copy-paste anti-pattern. Given below is a basic example explaining traits functionality.
[snippet id=”95″]
I declare the PriceUtilities trait with the trait keyword. The body of the trait looks very similar to that of a class. Now having declared and implemented the calculateTax() method in a single place, I go ahead and incorporate it into both the ShopProduct and the UtilityService classes.
The Bad
Besides traits being a nice mechanism for reusing code, they are also going to be a nightmare in the wrong hands. The dangers could be some of the following:
- Risk of altering a trait thinking it will alter one type of class using it, but may introduce issues with other classes using it.
- The idea of traits being used across multiple classes could potentially lead to conflicts in naming. You can solve it using insteadof keyword, but it’s now more things to worry about.
The Ugly
Traits are essentially language assisted copy and paste. The potential abuse of this feature is quite significant. Let’s go over some of it’s concerns
- Tight Coupling
Since traits are resolved at compile-time, the use is no different from extends in the sense that it tightly couples the trait implementation to the using class. This can actually reduce the re usability and utility of the class itself. - Testability
Traits are essentially static access in disguise and static code is bad. They usually introduce these code smells:- Tight coupling, no way to exchange at runtime.
- Static code cannot be overwritten through inheritance
- Not testable in isolation
- Single Responsibility Principle
There are definitely cases where traits can be used to implement common functionality where inheritance cannot. However there’s also a temptation to implement all sorts of code in a trait and the available runtime object really does many things than needed.
- Understandability
Right now, to understand what a class you need to traverse up the inheritance chain to determine how it works. Traits introduce horizontal inheritance and class in the chain can have an arbitrary number of traits associated with it. This can make understanding a code base significantly harder.
Summary:
Traits help solve a specific problem of reuse between classes – but must be used with caution as a rule of thumb thinking about how we can solve the problem with aggregation can help avoid it in most cases.