The last few weeks were more than hectic for me. We are approaching the end of a bigger project, and starting a new one, so I didn't have much time for my blog. Anyhow... during the design phase of the newer project I came across a very interesting design problem. We are developing an algorithm that has a pretty rigid structure at a higher level with steps that are invariable, and other (more heuristic) steps that can vary independently. Since we want to be able to quickly replace the heuristic steps of the algorithm (maybe even at runtime), and have a clean design with as few code duplication as possible I had to come up with a way to decouple these steps from the higher level structure of the algorithm.
The first solution that came to my mind was using the Template Method design pattern as described in the excellent book on Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides. While their version comes with many advantages, in this particular situation it has a quite obvious drawback: it uses inheritance, which would make it less convenient to adjust the algorithm, and vary the different heuristic steps independently, and downright impossible to do this at runtime.
I decided to use an event based approach. Instead of defining pure virtual functions for the hooks I defined callbacks similar to events. When the algorithm runs on a callback during execution, it calls the function(s) registered for it. There is however a significant difference between these callbacks and traditional events: in some cases you may want to make sure, that one and only one function is registered for a specific callback, and you may also want to use the return value of the function for something more complex. (Some implementations of event handling require the return value to be a boolean, and use it to prevent further listeners from getting executed when one of the listeners returns false.)
The first problem can be solved by overriding the addListener method with one that first removes all previous listeners, and adds the new one, or by implementing your own event handling. (My choice being the reimplementation.)
To address the second problem one can either use a custom implementation, or pass a pointer that can be used to provide a return value. I'd prefer the second solution, as it allows for a one-to-many relation when used wisely. Some scripting languages however do not provide a way to pass around pointers, which can be very annoying. In that case you may want to use the return values instead. (If you want to stick to passing pointers, you can try to pass a class instead with a "return value" public variable... BUT I don't really have to explain why you should definitely avoid that! It's pretty far from being an elegant solution.)
As a closure to this blog entry, let me go through the pro-s and con-s of this event based approach towards "Template Methods" as compared to the solution of the "Gang of Four". (Well... at least as far as I see it now!)
- The different steps of the algorithm can be changed during run time. For example you can use a separate configuration file that specifies the methods used.
- You don't have to use multiple inheritance or complex inheritance chains to have independently varying parts within the algorithm.
- In general we used object composition instead of inheritance, which is more favorable.
- Some hooks may even have more then one functions listening to them, however this may not always be desirable.
- Since hooks are not part of the main class, they do not access protected variables. You may have to pass a relatively large portion of your class by hand, if you don't want to make these variables public.
- The algorithm may get scattered among several different classes, which acts against transparency.
- It becomes more tedious to document your code: due to the protected variables being passed around for hooks, function calls become more complex, and hence the more things you need to write about.
All in all, I would not use the event based approach, unless the algorithm has steps that need to be varied independently, since it is less transparent than a classic Template Method. In this particular case however it turned out to be quite useful.