This is a mobile version, full one is here.
Yegor Bugayenko
7 March 2017
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.
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 literally 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 but 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.