Saturday, 17 January 2009

Item 16: favour composition over inheritance

In this item, the author discusses why it's often preferable to use composition instead of inheritance when extending the functionality of classes. He gives a number of reasons why this is, for example by arguing that classes that inherit from base classes and override methods in these are much more tightly coupled to these classes than ones just using the public interface of the base classes. An example that attempts to provide counts for number of inserts into a HashSet is given. For this example, forwarding or wrapper classes are presented as an alternative based on composition.

A couple of examples from the JDK are given that are examples of bad usage of inheritance where composition would have been much preferable: Stack extending Vector, and Properties extending Hashtable. In these example, the extended classes end up with a very fat interface that includes the full interface of the base classes even though some methods don't make sense for the derived class and shouldn't be used. I've come across the first one of these myself and been very surprised about it, it really is a bit of an abomination. For a start, there is no Stack interface, as there is for Map, Set, List etc. Also, it means that with a Java Stack, you can do very un-stack-like operations like inserting into the middle of it etc. And, once the class has been published with this API, it becomes impossible to change it to a different implementation of Stack of course, without extending it from Vector, as client code may depend on the Vector+Stack API that was published.

This advice is definitely useful, and probably well known to anyone who's worked with an OO language for any length of time. Over-use of inheritance is a typical beginner's mistake, I find, when it comes to OO design. I certainly made those mistakes when I started doing OO (in C++). I guess it's partly because of how OO is taught; inheritance and polymorphism are usually a major topic in any OO course. Also, I guess it's such a neat idea when you first come across it that it's tempting to try to fit every design problem into an inheritance hierarchy.

I think there are other reasons as well for preferring composition over inheritance that Bloch doesn't get much into in this item. A design using composition is much more flexible, for a start the relationships between objects can be changed at runtime (actually, he does mention that in his wrapper example). Also, testability is a major advantage. Classes interacting through interfaces can much more easily be used in isolation and tested with the help of stubs or mock classes. And, I think it's easier to provide simpler, cleaner classes that each have a clearly defined responsibility when they are completely separate. Anyone who's ever tried to debug a deep class hierarchy when methods override methods that override methods... knows how messy that can get.

Jan Stette

No comments: