QR code

Traits and Mixins Are Not OOP

  • Odessa, Ukraine
  • Modified on
  • comments

OOPoop java

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
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 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.

sixnines availability badge