Private Toolbox: An Anti-Pattern
Posted by matijs 10/04/2016 at 09h21
This is an anti-pattern that has bitten me several times.
Suppose you have an object hierarchy, with a superclass Animal, and several
subclasses, Worm, Snake, Dog, Centipede. The superclass defines the abstract
concept move
, which is realized in the subclasses in different ways, i.e., by
slithering or walking. Suppose that due to other considerations, it makes no
sense to derive Worm and Snake from a SlitheringAnimal, nor Dog and Centipede
from a WalkingAnimal. Yet, the implementation of Worm#move
and Snake#move
have a lot in common, as do Dog#move
and Centipede#move
.
One way to solve this is to provide methods walk
and slither
in the
superclass that can be used by the subclasses that need them. Because it makes
no sense for all animals be able to walk and slither, these methods would need
to be accessible only to subclasses (e.g., private in Ruby).
Thus, the superclass provides a toolbox of methods that can only be used by its subclasses to mix and match as they see fit: a Private Toolbox.
This may seem an attractive course of action, but in my experience, this becomes a terrible mess in practice.
Let’s examine what is wrong with this in more detail. I see four concrete problems:
- It is not always clear at the point of method definition what a method’s purpose is.
- Each subclass carries with it the baggage of extra private methods that neither it nor its subclasses actually use.
- The superclass’ interface is effectively extended to its non-public methods,
- New subclasses may need to share methods that are not available in the superclass.
The Animal superclass shouldn’t be responsible for the ability to slither and to move. If we need more modes, we may not always be able to add them to the superclass.
We could extract the modes of movement into separate helper classes, but in
Ruby, it is more natural to create a module. Thus, there would be modules
Walker and Slitherer, each included by the relevant subclasses of Animal. These
modules could either define move
directly, or define walk
and slither
.
Because the methods added in the latter case would actually makes sense for the
including classes, there is less need to make them private: Once could make a
instance of Dog walk, either by calling move
, or by calling walk
directly.
This solves all four of Private Toolbox’ problems:
- The module names reveal the purpose of the defined methods.
- Subclasses that do not need a particular module’s methods do not include it.
- The implementor of Animal is free to change its private methods.
- If a new mode of transportation is needed, no changes to Animal are needed. Instead, a new module can be created that provides the relevant functionality.