I’m in the middle of Practical Object-Oriented Design in Ruby, and I just found the best discussion of composition vs inheritance that I think I have ever found.
Here’s a quote:
Think of [inheritance] this way: For the cost of arranging objects in a hierarchy, you get message delegation for free. (p. 184)
There we go! No religious fervor, no arcane arguments about the purity of type inference, no dogmatically reiterating “composition over inheritance.” Just the simple statement that, if you’re willing to live with being stuck in a hierarchy, the objects will know how to delegate method calls up the hierarchy automatically. Metz goes on to argue that if we make a hierarchy, changes at the top of the hierarchy have far-reaching effects, which is both a blessing and a curse:
Inheritance, therefore, is a place where the question, “What will happen if I’m wrong?” assumes special importance. (p. 186)
From this, Metz argues that composition is preferable unless we have a specific reason to use inheritance. For example, if all our Rails controllers were polluted with explicit delegations to base classes for checking routes, validating CSRF tokens, and rendering stylesheet link tags, they would be much harder to read. On the other hand, making a base class called “Vehicle” just because your application deals with bicycles and cars is probably a huge waste of time, and can make you really regret it later; bicycles and cars don’t seem to share enough functionality to bother inheriting one from the other. (Also, for an excellent discussion about the perils of making your class hierarchy too much like the way a human would categorize the nouns in your business domain, see Eric Lippert’s excellent series Wizards and Warriors)
Metz is always arguing that we should be reasoning about messages not objects, that we make objects just so that we can send messages somewhere. Can you list off hand a few messages that it makes sense to send to a bicycle or a car, that you would be comfortable having automatically delegated to some object that doesn’t know whether it’s a bicycle or a car?
It really depends on the business use I suppose. Let’s say, borrowing from Metz’s examples, that we’re running a company that rents bikes and cars to people visiting some city. Given this situation, I can imagine a few messages that might be sent to both bicycle or car:
is rented? or
needs maintenance or
price per day come to mind. But none of these things are specific to vehicles; they just mean at that object “behaves like a rentable object,” not that it is a rentable object.
When I was first learning programming, someone explained inheritance vs. composition to me like this:
is_a relationships are for inheritance, and
has_a relationships are for composition. Metz adds a third category:
behaves_like_a is for interfaces. So in our rent-a-bike-or-a-car store, we would say that a bike
behaves_like_a rentable. Using a ruby mixin, it is perfectly possible to reuse the code for the rentable without causing the bike and the car to share a base class that knows about renting things. This arrangement of code leaves open the perfectly reasonable possibilities of having bikes that are not for rent and renting things that are not vehicles.
This is a place where code and reality diverge a bit. I think the
is_a relationship gets us in trouble because there are many more
is_a relationships in the real world than it makes sense to represent in code. Sure a bike and a car both have an
is_a relationship with vehicle, but that doesn’t mean that it makes sense in code to arrange the situation so that any message a bike doesn’t answer explicitly gets sent to the
Vehicle class. From your application’s perspective, it makes much more sense to say that bike and car are both entities in your database, so they inherit from
ActiveRecord or some similar class.
I’ve read about design patterns quite a bit, but Metz was the first author to present the perfectly reasonable
behaves_like_a pattern for interface use: A bike
has_a bike chain (composition),
is_a database record (inheritance), and
behaves_like_a rentable object (interface implementation or duck typing). I know this book helped me understand that aspect of design, and I think reading it will improve my code.
So maybe the next time that we’re about to get into an argument over whether inheritance is evil, or whether composition is always good, someone will yell “Haha! False dilemma! You should be using a
behaves_like_a relationship, and then we won’t have such an argument.” Or something like that.
Till next time, happy learning!