“Favor composition over inheritance”, commonly attributed to a 1994 Book, Design Patterns, by a bunch of very smart people known as “the Gang of Four”, is often given as a solid piece of software development advice. In this article I’m going to try and explain why and how you should favor composition over inheritance – and in particular, how you can tailor it to your Ruby code.
So What’s Wrong with Inheritance
I’m going to assume, for the purposes of this article, that you’re all as a bad a programmer as I am. I’m also going to assume you make similar mistakes and find yourself in similar tangles as a project goes from ‘this-is-going-to-be-awesome’ green field development to ‘oh-no-why-did-i-do-that’ maintenance mode. So, if this reads like a bit of a confession piece, it’s because I’ve made all these mistakes so you don’t have to (again).
Modelling the Real World
Many of my headaches in development have come from a misunderstanding of what inheritance is and why you should use it (and indeed, I’m sure I’m about to offer an incorrect definition here). A beginner programmer – be it in Ruby, Java or whatever – is tempted to look at inheritance and think “neat, I make
Foo inherit from
Bar and I get a all the features of
Bar for free.” And it’s true,
Foo < Bar will do just that. What that doesn’t quite capture is, in anything but the smallest of programs, saying ‘Foo is a Bar’ only holds for a limited time.
Eventually, Foo and Bar will diverge enough that the correct statement becomes more like ‘Foo shares some features of Bar’. By now of course, it’s too late and you’re 3 months into a project and stuck with two tightly coupled classes which are hard to test and difficult to separate.
Often, inheritance issues manifest themselves midway through a project or if requirements change (and they will). This is because a relationship which looks like it can only be modelled as a ‘is-a’ relationship can be expressed as a ‘has-a’ relationship – and a ‘has-a’ relationship is a whole lot more flexible.
Let’s look an example. Say we’re trying to model the world of development. We’ve got
Testers. You could reasonably expect any beginner text box to come up with something like this for the class structure.
Perhaps every employee has a
@salary and a
Tester has a
test method, while a
Developer has a
make_bugs method. Now, we need to add another class, the friendly
Intern. People of this type, the spec says, are able to
test. They also don’t take a
@salary – being effectively slave labour. An
Intern is almost an employee, almost a
Developer and almost a
Tester and there isn’t a clear place they can fit into the class hierarchy.
In this instance, we start off by saying that a
Employee – which was true for a time, but now we’ve found that a
Developer‘s behaviour is only sometimes a subtype of an
Employee‘a behaviour. We’ve got two options here. We can change our expression of the class so a
Developer has some sort of
Employment – effectively turning the method of payment into a has-a relationship. Alternatively, we could turn the task performed by a
Developer into a has-a relationship. This is where composition comes in.
Composition to the Rescue
Traditional composition, which works pretty much the same way in pretty much every OO language, involves moving those features that we want to share into a class in their own right. So, we’d have something like.
Ruby also provides a second way to get much the same effect: Modules. Using modules we can inject this bug producing functionality into any
Person class by simply writing a mixin.
We can do the same for the testing functionality. Now our
Intern can just be a
Person who has-a ability to code and test.
As we’ve seen here, inheritance isn’t the only method of code sharing available to you and, in many cases, is not the best choice. Good use of inheritance should focus on the representing the ‘real world’ relationships between objects and not on the grunt work of code sharing.
‘White box’ and it’s twin ‘Black box’ are terms used in a variety of fields and mean a variety of different things. When we talk about box colour in terms of code re-use, specifically inheritance, we’re really talking about visibility.
A white box class in which all it’s methods are public (ignoring the fact that in Ruby we can see the unseeable with a sneaky call to
send) – that is to say, we can often modify the data in the object whether or not we’re doing the operation inside the class or externally.
A black box does not allow this kind of access to it’s internal state. It provides a defined set of input methods and a defined set of output. What happens in the middle shouldn’t concern us. In strongly typed languages these inputs and output are usually codified using something like Java’s
In Ruby, unlike Java, there is no way to protect methods in a parent class from being used in a subclass. This means that each subclass has complete visibility of it’s parent class. This, in turn, causes us to care about a given classes ancestry whenever we use it.
Because we’ve got this visibility, we’ve also got a great temptation to override and manipulate methods from our parent. This is fine in most, if not all cases, but it does mean that we now have a contract between the implementation of the parent and the implementation of the child. We can now no longer change one without changing the other.
Think about your unit tests – specifically your ActiveRecord unit tests. ActiveRecord shares it’s features through the inheritance of
ActiveRecord::Base. Chances are, unless you used something like Mocha to great effect, that significant changes to ActiveRecord would break your test (let alone your code). This is a great example of where inheritance is, perhaps, not the most appropriate choice. Your
User model isn’t a type of
ActiveRecord::Base and shouldn’t pretend to be – and there’s not much point testing the saving of a record to a testing database when you can be pretty certain that when you hand over to active record you’ll be running well tested code anyway.
So, keep your
User model inheriting from active record – but implement key features which need to be shared or inherited from as small isolated classes or modules which.The business rules about a
User should’t be intertwined with the messy work of munging sql statement together.
Preferring composition over inheritance as your “go to” way of sharing code will force you to structure your application in such a way as to produce more classes (or modules) which serve a single purpose. In essence, you’ll be swapping a large whitebox implementation for many blackbox implementations.
As always, I’ll be keeping an eye on the comments and if I’ve been unclear or, indeed, wrong about anything I’d love to hear about it. Have you recently started using composition more thoroughly? Do you disagree that inheritance should not be the preferred method of code sharing? Can you think of any good examples to illustrate the whole dilemma?