Traits and Mixins Are Not OOP
Let me say right off the bat that the features we will discuss here are pure poison brought to object-oriented programming by those who desperately needed a lobotomy, just like David West suggested in his Object Thinking book. These features have different names, but the most common ones are traits and mixins. I seriously can’t understand how we can still call programming object-oriented when it has these features.
Fear and Loathing in Las Vegas (1998) by Terry Gilliam
First, here’s how they work in a nutshell. Let’s use Ruby modules as a sample implementation. Say that we have a class Book
:
class Book def initialize(title) @title = title end end
Now, we want class Book
to use a static method (a procedure) that does something useful. We may either define it in a utility class and let Book
call it:
class TextUtils def self.caps(text) text.split.map(&:capitalize).join(' ') end end class Book def print puts "My title is #{TextUtils.caps(@title)}" end end
Or we may make it even more “convenient” and extend
our module in order to access its methods directly:
module TextModule def caps(text) text.split.map(&:capitalize).join(' ') end end class Book extend TextModule def print puts "My title is #{caps(@title)}" end end
It seems nice—if you don’t understand the difference between object-oriented programming and static methods. Moreover, if we forget OOP purity for a minute, this approach actually looks less readable to me, even though it has fewer characters; it’s difficult to understand where the method caps()
is coming from when it’s called just like #{caps(@title)}
instead of #{TextUtils.caps(@title)}
. Don’t you think?
Mixins start to play their role better when we include
them. We can combine them to construct the behavior of the class we’re looking for. Let’s create two mixins. The first one will be called PlainMixin
and will print the title of the book the way it is, and the second one will be called CapsMixin
and will capitalize what’s already printed:
module CapsMixin def to_s super.to_s.split.map(&:capitalize).join(' ') end end module PlainMixin def to_s @title end end class Book def initialize(title) @title = title end include CapsMixin, PlainMixin def print puts "My title is #{self}" end end
Calling Book
without the included mixin will print its title the way it is. Once we add the include
statement, the behavior of to_s
is overridden and method print
produces a different result. We can combine mixins to produce the required functionality. For example, we can add one more, which will abbreviate the title to 16 characters:
module AbbrMixin def to_s super.to_s.gsub(/^(.{16,}?).*$/m,'\1...') end end class Book def initialize(title) @title = title end include AbbrMixin, CapsMixin, PlainMixin def print puts "My title is #{self}" end end
I’m sure you already understand that they both have access to the private attribute @title
of class Book
. They actually have full access to everything in the class. They litterally are “pieces of code” that we inject into the class to make it more powerful and complex. What’s wrong with this approach?
It’s the same issue as with annotations, DTOs, getters, and utility classes—they tear objects apart and place pieces of functionality in places where objects don’t see them.
In the case of mixins, the functionality is in the Ruby modules
, which make assumptions about the internal structure of Book
and further assume that the programmer will still understand what’s in Book
after the internal structure changes. Such assumptions completely violate the very idea of encapsulation.
Such a tight coupling between mixins and object private structure leads to nothing by unmaintainable and difficult to understand code.
The very obvious alternatives to mixins are composable decorators. Take a look at the example given in the article:
Text text = new AllCapsText( new TrimmedText( new PrintableText( new TextInFile(new File("/tmp/a.txt")) ) ) );
Doesn’t it look very similar to what we were doing above with Ruby mixins?
However, unlike mixins, decorators leave objects small and cohesive, layering extra functionality on top of them. Mixins do the opposite—they make objects more complex and, thanks to that, less readable and maintainable.
I honestly believe they are just poison. Whoever invented them was a long ways from understanding the philosophy of object-oriented design.
You may also find these related posts interesting: Vertical vs. Horizontal Decomposition of Responsibility; A Compound Name Is a Code Smell; Gradients of Immutability; Anti-Patterns in OOP; How an Immutable Object Can Have State and Behavior?;
Reference: | Traits and Mixins Are Not OOP from our JCG partner Yegor Bugayenko at the About Programming blog. |