Originally posted on: Big ball of mud
When I started programming, there was a design pattern among all the others that surprised me for its effectiveness. This pattern was the Template Method pattern. While I proceeded through my developer career, I began to understand that the inconsiderate use of this pattern could lead to a big headache. The problem was that this pattern promotes code reuse through class inheritance. With functional programming became mainstream, this pattern can be revised using lambda expressions, avoiding any inheritance panic.
The original pattern
It's the year 2004. Martin Fowler had just published one of its most popular post Inversion of Control Containers and the Dependency Injection pattern (IoC). The pattern is a concretization of the famous Hollywood Principle, which states
Don't call us, we'll call you
Every Java framework in those years implements that principle: Struts, Spring MVC, Hibernate, and so on. However, the IoC was not a fresh new idea. It took its roots from a well-known design pattern of the Gang of Four, the Template Method Pattern.
The intent of the pattern is the following.
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.
Uhm, it looks promising. I think it is better to make a concrete example. The example is taken directly from the GoF's book.
Consider an application framework that provides Application
and Document
classes. The Application
class is responsible for opening existing documents stored in an external format, such as a file. Whereas, a Document
object represents the information in a document once it's read from the file.
So, the objective is to create a structure with these classes that enable to add easily new kinds of documents.
In our example, the open algorithm is made of the following tasks.
- Check if the document can be opened
- Create an in-memory representation of the document
- Eventually, make preliminary operations
- Read the document
Translating this into some dirty code, we obtain the following class.
trait Application {
// Template method
def openDocument(fileName: String): Try[Document] = {
Try {
if (canOpen(fileName)) {
val document = create(fileName)
aboutToOpen(document)
read(document)
document
}
}
}
// Hook method
def aboutToOpen(doc: Document) = { /* Some default implementation */ 34}
// Primitives operations
def canOpen(fileName: String): Boolean
def create(fileName: String): Document
def read(doc: Document)
}
As you can see, the method openDocument
defines a template of the algorithm needed to open a document. For this reason, it is called a template method. Whereas, all other methods are abstract or provide defaults implementation for a task. The formers are called primitive operations; The latter is called hook operation, instead.
Implementing an application that can open CSV file means to extend the Application
trait, implementing correctly the methods that were left abstract.
class CsvApplication() extends Application {
def canOpen(fileName: String): Boolean = { /* Some implementation */ }
def create(fileName: String): Document = { /* Some implementation */ }
def read(doc: Document) = { /* Some implementation */ }
}
Drawbacks
One of the main consequence of the original Template Method pattern is code reuse. In this blog, we don't like code reuse. In the Dependency post, we learned that we should avoid code reuse in favor of behaviour reuse. Code reuse leads to an increase of dependency between classes, resulting in architectures, which components are tightly coupled.
Bad. Really, bad.
Moreover, the abuse of the Template Method pattern tends to generate into deep hierarchies of concrete classes. Let's return to our example. One day, your boss asks you to extend the above code to handle more than one type of storage. He wants the software to be able to read both from local filesystem and the cloud, or from distributed filesystems like HDFS.
Easy, you think. We already have a primitive method, create
(read
is reserved to unmarshal bytes into object-oriented structures), that I can override to allow my program to read from any storage. You are a smart boy.
class CsvApplication() extends Application { /* ... */ }
class CsvOnHDFSApplication() extends Application {
/* ... */
override def create(fileName: String): Document = { /* Some implementation */ }
}
class CsvOnCloudApplication() extends Application {
/* ... */
override def create(fileName: String): Document = { /* Some implementation */ }
}
// And so on...
At some point, you need to change the canOpen
in the CsvApplication
. Unfortunately, the new implementation does not fit the behavior in the CsvOnCloudApplication
class. Then, you need to override also the canOpen
method in this class. One day, you will have to change the implementation of another primitive method in some other class in your hierarchy. And the story will repeat over and over again.
Do you see the problem? You miss the maintainability of your classes. You do not know which behavior is implemented in which class. Every time you modify a class, it is difficult to predict which other classes you need to change.
What can we do at this point? Which alternatives do we have?
Favor object composition over class inheritance
The above motto, coined by the GoF, remembers us one of the principles that should guide us every time we develop something using an object-oriented programming language.
Template Method pattern: the right way
It is not difficult to image how we are going to change the original pattern to use composition, instead of inheritance. We are going to extract all the behaviors enclosed in primitive methods into dedicated classes. So, in our example, we can identify the following types.
// Subtypes of this trait give access to different types of storages
trait Storage {
def openDocument(fileName: String): Try[Document]
def canOpen(fileName: String): Boolean
def create(fileName: String): Document
}
// Different types of file format can be read simply defining
// subtypes of this trait
trait Reader {
def read(doc: Document): Document
}
In this way, our original Template type becomes the following.
class Application(storage: Storage, reader: Reader) {
def openDocument(fileName: String): Try[Document] = {
Try {
if (storage.canOpen(fileName)) {
val document = storage.create(fileName)
storage.aboutToOpen(document)
reader.read(document)
}
}
}
}
Whoa! We do not need an abstract type anymore! We do not need to use class inheritance either! Everything can be resolved using object composition during the instantiation of the Application
class.
val hdfsCsvApplication = new Application(new HdfsStorage(), new CsvReader())
val cloudCsvApplication = new Application(new CloudStorage(), new CsvReader())
// And so on...
As a plus, we reduced the dependencies of the overall architecture, avoiding all those annoying subclasses.
The Programmable Template Method
Most of the modern programming languages have functions as first-class citizens. A variant of the Template Method pattern uses lambdas as implementations of primitive methods. I like to call it Programmable Template Method.
As Joshua Bloch wrote in the third edition of Effective Java,
The Template Method pattern [..] is far less attractive. The modern alternative is to provide a static factory or constructor that accepts a function object to achieve the same effect.
The trick is passing functions in place of primitive methods during object instantiation. So, let's turn our original Application
trait into a concrete class receiving some functions as input during the construction process.
class Application(
// Functions used to 'program' the behavior of the application
canOpen: String => Boolean,
create: String => Document,
read: Document => ()
) {
// Template method
def openDocument(fileName: String): Try[Document] = {
Try {
if (canOpen(fileName)) {
val document = create(fileName)
aboutToOpen(document)
read(document)
document
}
}
}
def aboutToOpen(doc: Document) = { /* Some default implementation */ 34}
}
val app = new Application(
(filename: String) => /* Some implementation */,
(filename: String) => /* Some implemenation */,
(doc: Document) => /* Some implementation */
)
In this way, you have not to declare a new type for each behavior you want to implement, just create a lambda expression, and pass it to the constructor during object creation.
The Scala way
So far, so good. We all know that the Scala language can do better than a simple object composition. Scala has some very powerful constructs that allow us to use some smarter versions of the Template Method pattern.
Mixins
The first construct we are going to use is Scala mixins. Mixins are traits which are used to compose a class. Using mixins, we can add some code to a class without using inheritance. It's a concept very similar to the composition.
Revamping the first Application
trait we presented in this post, we can use mixins to implement the Template Method also in this way.
trait Application {
def openDocument(fileName: String): Try[Document]
def canOpen(fileName: String): Boolean
def create(fileName: String): Document
def aboutToOpen(doc: Document) = { /* Some default implementation */ 34}
def read(doc: Document)
}
// Hdfs storage implementation
trait HdfsStorage {
def openDocument(fileName: String): Try[Document] = /* HDFS implementation */
def canOpen(fileName: String): Boolean = /* HDFS implementation */
def create(fileName: String): Document = /* HDFS implementation */
}
// Csv reader implementation
trait CsvReader {
def read(doc: Document): Document = /* CSV implementation */
}
// Creating the application using the proper implementations
val hdfsCsvApplication = new Application with HdfsStorage with CsvReader
As you can see, we can mix any trait during the instantiation process of another trait. We achieved composition using mixins, using a native approach.
Functional programming: currying and partial application
Many of us abandoned the object-oriented path after the functional way enlighted them. Is the Template Method pattern worth also in functional programming? Well, in some way the answer is "yes".
In functional programming, there is a technique called currying. The name "currying" becomes from the mathematician Haskell Curry, who was the first to use this technique.
Currying transforms a multi-argument function so that it can be called as a chain of single-argument functions
For the sake of simplicity, let's take a function that sums two integers.
def sum(a: Int, b: Int): Int = a + b
Then, using currying, we can derive from sum
its curryfied form.
def curryfiedSum(a: Int)(b: Int): Int = a + b
If we pass only the first parameter to curryfiedSum
, the result will be a new function with only one parameter left. In mathematical jargon, the function was partially applied. Using this approach, we can quickly obtain a new function that sums five to any integer number.
def sumFive(x: Int): Int = curryfiedSum(5)
Well, now that we have given the necessary background, we can reveal which is the link between currying and partial application and the Template Method Pattern. Let's define a function that repeatedly applies a lambda to a list of integer numbers. We can call it aggregate
. Using this function, we can identify two new functions, the factorial n! and the sum of all numbers up to n.
def aggregate(neutral: Int, op: (Int, Int) => Int)(n: Int): Int =
if (n == 0) neutral
else op(aggregate(neutral, op)(n-1), n)
// Factorial of integers from n to 0
def factorial = aggregate(1, _ * _)_
// Summing integers from n to 0
def sum = aggregate(0, _ + _)_
As you can see, the function aggregate
defines the body of the general algorithm, whereas the first two arguments define the variable parts of the algorithm. Template method and primitive operations: It's the same approach of the Template Method pattern.
Do you see it? Awesome!
Conclusions
Summarizing, we started presenting the original Template Method design pattern, as shown by the GoF. Following the principle prefer composition over inheritance, we highlighted all the problems with the original approach. Then, we brought some procedures that correct the original issue.
Using composition, lambdas and curryfication, we were able to bring the Template Method pattern in the 21st century.
References
- Inversion of Control Containers and the Dependency Injection pattern
- Inversion of control
- Chapter: Introduction (page 20). Design Patterns, Elements of Reusable Object Oriented Software, GoF, 1995, Addison-Wesley
- Chapter: Template Method Pattern (page 325). Design Patterns, Elements of Reusable Object Oriented Software, GoF, 1995, Addison-Wesley
- Item 44: Favor the use of standard functional interfaces. Effective Java, 3rd edition, Joshua Bloch, 2018, Addison-Wesley Professional
- Class composition with mixins
- Functional design patterns, Part 1
Top comments (0)