IS-A IS-A HAS-A
All of the pain caused by inheritance can be traced back to the fact that inheritance forces ‘is-a’ rather than ‘has-a’ relationships. If class R2Unit extends Droid, then a R2Unit is-a Droid. If class Jedi contains an instance variable of type Lightsabre, then a Jedi has-a Lightsabre.
The difference between is-a and has-a relationships is well known and a fundamental part of OOAD, but what is less well known is that almost every is-a relationship would be better off re-articulated as a has-a relationship.
I’m in the middle of dealing with some ActiveRecord, um, ‘features,’ so I don’t have time to write an interesting, thoughtful essay. Nor do I have time to write an overly long and meandering essay as Raganwald readers have come to expect. So instead, let’s pretend there’re six or seven closely typewritten pages of self-indulgent bloggery leading up to…
IS-A IS-A HAS-AWhen classical OO languages (including Ruby and Java) implement inheritance, what actually happens is this: First, the subclass acquires the interface of the superclass (latently typed languages like Ruby don’t do this explicitly, but it is still true). Second, under the hood where you can’t see it, the compiler makes room for a reference to the superclass and arranges for methods that the subclass does not implement to be delegated to the superclass.
In other words, whenever you declare that Child IS-A Person, the compiler writes “Child HAS-A Person” and “Child BEHAVES-LIKE-A Person” and “Child DELEGATES-TO Person” for you.
The right way to look at single inheritance is therefore that it is a
specialization of composition. Like all specializations, it does less. It is more specific. And therefore, when it is exactly what you mean to accomplish, it is more useful: you need fewer lines of code to express it, the compiler optimizes the relationship for you, and since it is a very well-known idiom, it expresses your intent clearly to other programmers.
On the other hand, when single inheritance is not exactly what you mean to express, you have two choices: you can greenspun some more general feature out of it, adding accidental complexity to your code and making it less readable, or you can use the more general purpose, flexible tool: composition.
Like all Golden Hammers, seeing every OO problem as a single inheritance nail leads to trouble. Single inheritance is probably Turing Equivalent: given enough determination, you can probably write any arbitrary program if you are prepared to twist your code into knots as it bows down and sacrifices readability to the inheritance demon.
However, breaking free of its grip is simple: you need only see inheritance as a tool like any other tool: it is not superior to the others at your disposal. It is not the goal. A nice pyramid of classes and subclasses is not your objective when you set out to design a program. When you see it as just another chapter in the book, you see when to use it and when to set it aside.
And in fact, if you see it as a particularly
thin chapter in the book, you may be surprised how much easier it is to work with OO programs. And if you see it as a couple of pages in the chapter on composition and delegation… you will be on the road to understanding.