Refactor to Delegation in preference to Abstract Classes
Minimize, eliminate, delegate, and routinize. Decide what's important and forget the rest. - Donna N. Douglass.
I've noticed a few times that factoring the difference between subclasses into explicit delegation results in cleaner code that is easier to test, especially when I use mock objects. Perhaps this is a useful Mock Object testing pattern: Replace Duplicated Behaviour with Delegation Through an Interface.
I recently refactored some of the jMock code to hook
customisable formatting into the code that generated error messages
on test failure. The end result is that
InvocationMocker
objects - objects that represent
expectations or stubs - delegate to a Describer
interface when asked to give their description and the concrete
describer can be set when the object is constructed. In this way,
the objects of the generic framework can be configured to create
error messages that reflect the high level API used to compose them.
As part of the refactoring, I found a subclass of InvocationMocker that existed just to override the default describe implementation (to give no description, as it happens). I replaced instantiations of this class with instantiations of the base class with a custom Describer. This is a much cleaner design and much easier to test. The delegation of the descriptions to a Describer object is very easy to unit test with mock objects; the existing implementation and the overridden version were only tested in the acceptance tests and not actually unit tested at all.
If we had originally factored the differences between base class and subclass into a separate object, and defined the interaction with that object with an interface, the Describer design would have been already implemented by the time we needed it. We would also have been better able to test our classes. We should have listened to our tests and pulled out that interface when we originally wrote the subclass of InvocationMocker.
Update: Ivan Moore has posted a good article about refactoring inheritance into composition and delegation.