Tom McAdam

template vs decorator

We recently had a discussion in the office on when the decorator pattern should be used, and when the template pattern may be better. We came to the following conclusion:

If the system’s correct behaviour 1 relies on decorators being used in a specific configuration, use the template pattern to enforce and document it.

Let me explain the rationale behind this. It’s probably easier with the help of an example; we’ll use one that’s influenced by, but a simplified version of, something in the Atlas 3.0 codebase. Names have been changed, not to protect the guilty, but for simplicity.

We’re going to look at something that is responsible for saving things into the Atlas database, a ContentWriter. Let’s start with a UML diagram (the only one, I promise):

ContentWriter interface and implementations

Instantiation of the ContentWriter looks like this:

We’ll now look at how the same code looks using the template pattern. Here’s the template class, an AbstractContentWriter:

A concrete implementation in the form of a MongoContentWriter or a CassandraContentWriter would then be written, but I’ll omit them for brevity.

There are a number of benefits to the template approach. Firstly, it’s obvious the order in which things must happen; it’s not hidden in some configuration class somewhere (or worse, XML), but plain for everyone to see in the template class. Secondly, when there are multiple concrete implementations, or even instantiations, we’re sure that each of them is constructed in a consistent manner, and you’ll never forget to wire in a crucial piece of functionality when you’re in a rush.

Now you could use decorators and enforce the correct configuration through the judicious use of package visibility on constructors along with some factory methods, but we felt that the template pattern more strongly and clearly expresses the order in which things must be done.

The decorator pattern does still have its place. It’s great where you’ve got optional behaviours you’d like to add to an implementation, such as a caching wrapper. When used appropriately, it neatly separates concerns, making testing easier. However, the moment you start decorating classes with functionality that’s required for the system to operate as intended, it’s time to think again.

In hindsight the above conclusion may sound a little obvious, but I’m pleased we were able to distill our feelings into a rule which can now be applied to similar problems in the future. These things are quite subjective, so if you have a different view, we’d love to hear what you think.

1 That’s the same correctness as in the LSP

blog comments powered by Disqus