Gotcha #72: Making Template Methods Too Flexible
The Template Method pattern partitions an algorithm into invariant and variant parts. We specify an invariant algorithm as a nonvirtual member in the base class. However, this nonvirtual function allows customization of certain steps of its algorithm by derived classes. Typically, the algorithm invokes protected virtual functions that may be overridden in derived classes. (Note that the Template Method pattern has nothing whatever to do with C++ templates.)
This allows a base class designer the ability to guarantee the overall structure of an algorithm for every derived class while allowing an appropriate level of customizability of the algorithm:
class Base {
public:
// . . .
void algorithm();
protected:
virtual bool hook1() const;
virtual void hook2() = 0;
};
void Base::algorithm() {
// . . .
if( hook1() ) {
// . . .
hook2();
}
// . . .
}
A Template Method gives us a level of control between that of nonvirtual and virtual functions. Taking our base class design idioms together, it's instructive to see how much design constraint we can communicate to derived class designers through idiom alone:
class Base {
public:
virtual ~Base(); // I'm a base class
virtual bool verify() const = 0; // you must verify yourself
virtual void doit(); // you can do it your way or mine
long id() const; // live with this function or go elsewhere
void jump(); // when I say "jump," all you can ask is . . .
protected:
virtual double howHigh() const; // . . . how high, and . . .
virtual int howManyTimes() const = 0; // . . . how many times.
};
Many novice designers erroneously assume that a design should be maximally flexible. These designers often make the mistake of declaring the algorithm of a template method to be virtual, assuming that the additional flexibility would benefit derived class designers. Wrong. Designers of derived classes are helped most by an unambiguous contract in the base class. If generic code is expecting a particular general behavior from a template method, then that must be respected and implemented by derived classes.
|