To Inherit, or to Contain, That is the Question

In my early years as a software developer the moment I have learned something new I was eager to use it all around my projects. Right about that time the concept of inheritance hit me stronger than before and I was like “That’s cool! Let’s use it to its full potential”. And so it began. Inheritance invaded all the code that showed any signs of shared logic. I was extracting common methods into base classes to achieve reusable design. At first all was working out well: the code was DRY and reuse was flourishing all over it. Some doubts about that approach began to appear after a few weeks or so. I have succeeded to build a hierarchy of classes that served a single purpose - group common methods into base classes. As you all might have heard software changes. The moment I needed to make a change in any of the base classes I had to go to all specializations down the hierarchy and adapt to that change. The first time that I did that, I was: “Ok, I guess that’s the dues one has to pay to write reusable code”. That OOP book said inheritance is good. The book cannot be wrong. So I continued to do what the book says. By the fifth time I changed something in my base classes and had to propagate that change throughout my tree of objects, I said to myself: “Enough is enough. Inheritance cannot solve my duplicated logic issues. There has to be a better way to write reusable object-oriented software.” As the years passed by and my experience grew, it turned out that there are many ways to achieve that reusable design and inheritance have proved to be causing more pains than gains in most of the times. At one point of my professional career I even found out that what I was doing back then had a real name: a code smell named Refused Bequest. TL;DR: “Someone was motivated to create inheritance between classes only by the desire to reuse the code in a superclass but the superclass and subclass are completely different.”

Designing object-oriented software is hard, and designing reusable object-oriented software is even harder.

That was stated in the book “Design Patterns: Elements of Reusable Object-Oriented Software” in 1994 and today it still holds true.

An example of duplication

class Order
  def total_amount
    line_items.reduce(0) do |total, item|
      total + (item.count * item.price)
    end
  end
end

class Invoice
  def total_amount
    line_items.reduce(0) do |total, item|
      total + (item.count * item.price)
    end
  end
end

Here we have duplication in two classes that have nothing in common semantically but have the same repeating logic for calculating the total amount. A head first idea would be to pull up a method into a super class and call it a day.

Reuse through Inheritance

class Base
  def total_amount
    line_items.reduce(0) do |total, item|
      total + (item.count * item.price)
    end
  end
end

class Order < Base
end

class Invoice < Base
end

Ok, that code is bad and smells. I cannot even think of a good name for the super class. Now every time I had to change something in either Order or Invoice logic, I had to adapt the code so that the change does not break the other class. Or I can create some sort of type check inside the base class but that would break the Liskov substitution principle and our code will no longer be SOLID. Inheritance creates tight coupling among all the classes in the hierarchy chain. Specializations have access to methods and state of their ancestors. If you change a method in a base class you have to adapt to that change in all the specializations. Child objects inherit responsibilities from their parents and combine them with their own responsibility. Objects have many responsibilities violating the Single Responsibility Principle (SRP). There is no encapsulation between children and parents. Your hierarchy of objects may explode if you have many independent pieces depending on a base class. For example, let’s assume we have a ConnectionBase class abstracting database connectivity. On top of that you have multiple independent functionalities such as: caching, pooling, multiple engines, etc. If you try to solve such a puzzle with inheritance by splitting functionalities into base classes and creating a new specialization for each need, then you end up in a combinatorial explosion.

Reuse through Modules

module Base
  def total_amount
    line_items.reduce(0) do |total, item|
      total + (item.count * item.price)
    end
  end
end

class Order
  include Base
end

class Invoice
  include Base
end

In Ruby you can solve the puzzle above using modules to achieve sort of multiple inheritance. You can extract the caching and pooling and other “horizontal” aspects into modules and then include them into each of the specialized implementations for the different database engines. Now you do not have to create a new specialization for each combination that you need, but still this solution does not provide good encapsulation. Code is DRY but logic is still mixed up. Everything is still tightly coupled together. If you decide to change a module, then you have to go to all the classes that include it and change them. If the module depends on the object internal state then things can get even uglier. I would not advise anyone to extract methods out of a class into mixins (or modules) solely to DRY out shared logic. It is pretty much the same as if you would use utility (or helper) methods in an OOP design. And that’s really bad. You have messy code. You chop it off into small pieces. At the end of the day you still have messy code but now it is much harder to clarify the business domain logic. I would advocate for mixins if I have a really good reason behind, for example - horizontal concerns spreading over all of my controllers, for instance - authorization checks.

In Ruby including a module is inheritance.

Reuse through Inheritance in Rails

In Rails things may get really really messy when abusing inheritance. First, because of Rails magic. Second, because of Rails global state. It’s a fact that within your controllers, views and helpers you have access to session, to params, to cookies, to request and response objects and to any instance variable you set in any controller. Normally one request hits one controller action. But for the Rails magic to happen that controller needs to inherit from ApplicationController. So if you happen to assign an instance variable inside the ApplicationController then it will magically appear in all your controllers, views and helpers throughout your whole application. People also tend to abuse inheritance among Rails controllers by abstracting common code into their own base controllers. For example, if you are building a user management application with Rails you may be tempted to do something like this: UsersController inherits from ResourcesController inherits from ApplicationController. Setting an instance variable anywhere in the base of this chain will make it show up exponentially in the number of controllers you derive from the base ones. When the application gets bigger you will end up in the constant pain of Where did that variable come from? And that’s what we call an unmaintainable situation.

For example, one guy could do this to render the impersonated account user in the header of the application layout:

class ApplicationController < ActionController::Base
  def impersonate_user
    @user = current_account.user
  end
end

Or do the root of all evil - assignment in callbacks:

class ApplicationController < ActionController::Base
  before_action :impersonate_user

  def impersonate_user
    @user = current_account.user
  end
end

And another guy could do this to get the user to manage from the request:

class UsersController < ApplicationController
  def assign_user
    @user = User.find params[:id]
  end
end

or even worse:

class UsersController < ApplicationController
  def assign_user
    @user ||= User.find params[:id]
  end
end

because why not doing some premature optimizations via memoization.

Declaring an instance variable in a base controller will make you live in a constant fear that someone may re-assign that variable in a subcontroller and use it for something else, like in my example above. There would be no errors in production. But I can assure you that you will have really weird behavior and some serious security holes within your application. In short: avoid inheritance in Rails. Have in mind that including modules or concerns is also inheritance so you will be better off avoiding those guys as well. Instead, hide your state behind service objects and let your controllers call out to them to do the heavy lifting.

How to solve the user impersonation problem?

class ApplicationController < ActionController::Base
  # nothing to see here, go away human
end

module UsersHelper
  def impersonated_user
    current_account.user
  end
end

class UsersController < BaseApiController
  def assign_user
    @user = User.find params[:id]
  end
end

A helper to the rescue. We can use a view helper in our header partial to render the impersonated user. And by using it in the header partial, I mean: call it in the application layout and pass it as a local to the header partial. No state is set anywhere in the inheritance chain. The UsersController can safely assign and manage the user from the request.

Reuse through Composition

Let’s go back to summing line items amounts.

class TotalCalculator
  def sum(items)
    items.reduce(0) do |total, item|
      total + (item.count * item.price)
    end
  end
end

class Order
  def initialize(total_calculator)
    @total_calculator = total_calculator
  end

  def total_amount
    @total_calculator.sum line_items
  end
end

class Invoice
  def initialize(total_calculator)
    @total_calculator = total_calculator
  end

  def total_amount
    @total_calculator.sum line_items
  end
end

In this example, we have created a separate abstraction TotalCalculator that know only how to calculate a total from a list whose elements respond to count and price. It does not care if the caller is Order or Invoice or apples or cucumbers. It just performs the math through a clear interface. Now the Order and the Invoice classes can change internally as much as they want without affecting each other as long as they keep providing the same list that the TotalCalculator expects. We used Dependency Injection to inject the total calculator into the Invoice and Order. That would make our life much easier when unit testing, as compared to if we have had used a static method. In short: we have decoupled our code.

With composition you can put each piece of functionality into its own class. Then combine them all together into a working solution. That way each responsibility is isolated and encapsulated. Objects use one another through clear interfaces and never mix each other’s guts through internal state and non-public methods. They are loosely coupled. Each class is a black box to its container. If you change something inside one box no other box has to adapt to that change, because the second box works only with the first box public interface and does not know (and does not care) about the changes in its internal implementation. Each class has a single responsibility and only one reason to change. You build your abstractions around your business domain objects. You are not trying to pull common logic out of your code and build a hierarchy. Such a system where code is reused through containment and components talk to each other only through clear public interfaces is much closer to achieving reusable object-oriented software design.

Following SRP on each one of your abstractions can result in having a large number of small pieces that you have to fit together to build a bigger system. That gives you flexibility and DRYness but also can be harder to follow along and understand. There is a very good article on the topic that will definitely help you when deciding between larger or smaller bricks to use in your compositions: The Lego Way of Structuring Rails Code.

Reuse through Decorators

Another good pattern to rescue you from using inheritance is the Decorator pattern. Using decorators is more flexible because you can mix responsibilities in any combination and even nest or chain them to achieve unlimited possibilities. Since the decorator pattern is too bigger topic to fit in here, it deserves a blog post on its own. Also let’s keep this article focused on containment vs inheritance and not explore all the possibilities and patterns on writing reusable code.

Takeaway

Prefer composition over inheritance. Inheritance creates tight coupling among your classes. Compose complex systems from small abstractions around your business domain concepts.

References

  • Design Patterns: Elements of Reusable Object-Oriented Software, by Gamma, Helm, Johnson, and Vlissides
  • Practical Object-Oriented Design In Ruby, by Sandi