In my last post, I discussed in detail why exactly I don’t recommend the use of the Template for writing testable code. Here I want to illustrate an alternative to it, but before I do that, I want to talk about a more fundamental aspect of OO design.
Inheritance vs Composition
The way I see it, inheritance is an expensive and heavyweight mechanism in many languages, particularly those that only offer a single inheritance model, such as C# and VB .NET. It’s not quite the same in others like C++ (although multiple inheritance brings with it its own problems), but in .NET languages, you have to be really careful about creating inheritance chains. You can get yourself into all sorts of ugly situations where your inheritance model is too fat, with abstract methods that are unnecessary etc. etc., or too inflexible to allow you to do something different.
So in these situations, you should seriously consider composing behaviours out of smaller objects. You normally have to do some delegation to those small objects, but the flexibility you gain compared to inheritance fair outweighs the cost of that delegation.
In this arbitrary example, we have two types of people – a guitarist and a developer. They can perform operations that are completely unrelated. Using an inheritance model we’d struggle to compose these together i.e. Does Developer inherit from Guitarist? The other way around? What if we only wanted the functionality from the derived class and not the base class?
With composition we approach the problem slightly differently: –
We create two interfaces, one for each behaviour, and then create one concrete for each interface. Neither have any relationship with one another, but we are now in a position to simply use them as we see fit. In this example we make a composite DeveloperWhoPlaysGuitar type that inherits from neither class but implements both interfaces. It instead stores a private instance of both “real” objects and delegates the calls to them as appropriate: –
This gives the illusion that we have a type that inherits from both Guitarist and Developer, and is a much more flexible model than strict inheritance, albeit now we have had to create a third class (the composed type above) rather than just two types with inheritance.
Applying Composition to the Template problem
We can apply this approach to solving the Template problem, which leads to another design pattern completely – the Strategy pattern. In our original example with File Readers, we would probably redesign the API as follows: –
We now have a single class called FileProcessor, which was originally the template base class. However, instead of having abstract methods on the class, we have shuffled them off to a new interface called IFileReader. All the concrete readers implement this interface. The FileProcessor now takes in a single instance of this interface when we call ReadFiles(): –
This way, the FileProcessor is now far less tightly coupled to the actual reading of files etc.. Its job is now simply to perform business logic checks around the reading of files, and then delegate the actual grunt work of reading the file and checking validity of the file to the reader itself.
Testing out a Strategy-based API
We could easily test out our new FileProcessor class since it is no longer tightly bound to any implementation of IFileReader; we could just inject a fake in. Similarly, we can now test our new CsvFileReader much more easily than before since it now exposes a public API that we can call: –
No mocks involved. No ridiculously large arrangement of code. No awkward naming of tests. Improved readability. Simples!
Whilst quick and easy to consider and create a class hierarchy using Template pattern, it is exceptionally difficult to test, even for relatively simple template methods. What you end up doing is essentially an integration test between the template base class and the classes that implement the abstract methods.
The Strategy pattern involves the overhead of creating an extra type (the interface containing what would have been the abstract methods) and passing it in to the driver class. However, it proves to be far easier to test each implementation of your interface in isolation, as well as to test the runner / processor class out because we no longer have a tightly bound relationship between the two.
In addition, we gained some flexibility because we can now reuse the IFileReaders across other classes that may want that functionality, and not just the FileProcessor.