Friday, September 17, 2004

More about inheritance vs. composition

I was reading excellent Effective Java by Joshua Bloch, on a topic about typical pitfalls when implementing Object.equals method. One of the least obvious is related to extending equals' implementation. To quote an author:

There is simply no way to extend an instantiable class and add an aspect while preserving the equals contract.
The author ended up with advice to use composition instead of inheritance with for such cases. I mention this story now because my reflections on unit tests for class hierarchies led me to the same conclusion.

Actually, I have in mind one specific case of class hierarchy: heavy-weight base and a bunch of subclasses that override/extend a particular aspect of it. An example may be a Finder superclass to define basic code to search the database and specific subclasses for specific domain objects, like Person.

The problem lies in unit tests: if you write them both for base class and for each of subclasses you'll find a sheer amount of duplication in you test code. Moreover, it could be very difficult to eliminate because the code would be a bit different for each of the subclasses' test case. A possible way out could be to replace inheritance with composition. There are two choices:

  1. Aggregate former base class into attribute. This is usually a relatively simple and this allows you to "stub" base class for testing thus eliminating a duplication in test code. If the base class' interface was much richer than that of subclasses you may end up with a lot of delegate methods, though.
  2. Pass an instance of a former subclass to the base class in order to configure it with specialized data and code. This would also mean that all former subclasses must implement the same protocol (interface) that would be used by the base class. That's could be harder or even just not feasible, though - the hierarchy was created in the first place because you saw no other way to factor out the similarities. Additionaly, if you succeed, this refactoring could make this already heavy base class simply unmanageable.
Nice example of how TDD actually drives a design, isn't it?

update: renamed post title and improved reasoning for the two choices.


Post a Comment

<< Home