Template Method Pattern - Definition

The Template Method Design Pattern defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.

Processing files - An Example

In our application, we are processing either a XML or a JSON file (more types can be added in the future). To process the XML file, we first validate/verify the file’s checksum. Second, we parse the file to a JsonDocument. Third, we update the in-memory representation of the document (update some field) and finally write the updated document back.

The process for JSON is similar. But the logic to parse and write JSON files will be different from that of a XML.

public class JsonDocManipulator {
    public void processFile(File file) {
        validateChecksum(file);
        JsonDocument jsonDocument = parseFile(file);
        updateDocument(jsonDocument);
        writeUpdatedDocument(jsonDocument, file.getPath());
    }

    private void validateChecksum(File input) {
        System.out.println("Validating the file's checksum");
    }

    private JsonDocument parseFile(File input) {
        System.out.println("Parsing file as an JSONDocument");
        return new JsonDocument();
    }

    private void updateDocument(Document document) {
        System.out.println("Updating the document");
    }

    private void writeUpdatedDocument(JsonDocument jsonDocument, String path) {
        System.out.println("Writing JSON document");
    }
}
public class XmlDocManipulator {
    public void processFile(File file) {
        validateChecksum(file);
        XmlDocument xmlDocument = parseFile(file);
        updateDocument(xmlDocument);
        writeUpdatedDocument(xmlDocument, file.getPath());
    }

    private void validateChecksum(File input) {
        System.out.println("Validating the file's checksum");
    }

    private XmlDocument parseFile(File input) {
        System.out.println("Parsing file as an XMLDocument");
        return new XmlDocument();
    }

    private void updateDocument(Document document) {
        System.out.println("Updating the document");
    }

    private void writeUpdatedDocument(XmlDocument xmlDocument, String path) {
        System.out.println("Writing XML document");
    }
}
public class Document {
    //Details do not matter
}

public class JsonDocument extends Document {
    //Details do not matter
}


public class XmlDocument extends Document {
    //Details do not matter
}

You can see that there is a lot of duplication between JsonDocManipulator and XmlDocManipulator.

The logic for checkum validation is the same for both XML and JSON. Also, the logic to update a document does not depend on a particular document type (JsonDocument or XmlDocument) and instead is the same across all file types.

What if we use inheritance to move the common logic (checksum validation and updating document) to a superclass?

public class DocManipulator {
    protected void validateChecksum(File input) {
        System.out.println("Validating the file's checksum");
    }

    protected void updateDocument(Document document) {
        System.out.println("Updating the document");
    }
}
public class JsonDocManipulator extends DocManipulator {
    public void processFile(File file) {
        validateChecksum(file);
        JsonDocument jsonDocument = parseFile(file);
        updateDocument(jsonDocument);
        writeUpdatedDocument(jsonDocument, file.getPath());
    }

    private JsonDocument parseFile(File input) {
        System.out.println("Parsing file as an JSONDocument");
        return new JsonDocument();
    }

    private void writeUpdatedDocument(JsonDocument jsonDocument, String path) {
        System.out.println("Writing JSON document");
    }
}
public class XmlDocManipulator extends DocManipulator {
    public void processFile(File file) {
        validateChecksum(file);
        XmlDocument xmlDocument = parseFile(file);
        updateDocument(xmlDocument);
        writeUpdatedDocument(xmlDocument, file.getPath());
    }

    private XmlDocument parseFile(File input) {
        System.out.println("Parsing file as an XMLDocument");
        return new XmlDocument();
    }

    private void writeUpdatedDocument(XmlDocument xmlDocument, String path) {
        System.out.println("Writing XML document");
    }
}

This looks a bit better. But still the steps within processFile is the same across file formats.

Note: We are also violating a principle to not use inheritance to share code.

Applying the Template Method pattern

In our code, the processFile lists the steps of the algorithm to process a file. To apply the template method pattern, we will move this to the base class itself.

public abstract class DocManipulator {
    public void processFile(File file) {
        validateChecksum(file);
        Document document = parseFile(file);
        updateDocument(document);
        writeUpdatedDocument(document, file.getPath());
    }

    private final void validateChecksum(File input) {
        System.out.println("Validating the file's checksum");
    }

    private final void updateDocument(Document document) {
        System.out.println("Updating the document");
    }

    protected abstract Document parseFile(File input);
    protected abstract void writeUpdatedDocument(Document document, String path);
}
public class JsonDocumentManipulator extends DocManipulator {
    @Override
    protected Document parseFile(File input) {
        System.out.println("Parsing file as an JSONDocument");
        return new JsonDocument();
    }

    @Override
    protected void writeUpdatedDocument(Document document, String path) {
        System.out.println("Writing JSON document");
    }
}
public class XmlDocumentManipulator extends DocManipulator {
    @Override
    protected Document parseFile(File input) {
        System.out.println("Parsing file as an XMLDocument");
        return new XmlDocument();
    }

    @Override
    protected void writeUpdatedDocument(Document document, String path) {
        System.out.println("Writing XML document");
    }
}

Besides moving the template to the base class, it defines parseFile and writeUpdatedDocument as abstract methods. Each subclass has to override these and provide an implementation.

The processFile lists the individual steps of the algorithm. Thus, it is a template and is called the template method. Each step in the template calls a method. Some methods are declared abstract and some have concrete implementations in the base class. The methods having concrete implementation on the subclasses are the ones that do not change depending on the subclass and hence they are placed at the base class. The abstract method’s logic depends on a particular subclass (XML or JSON in our example).

A subclass’s responsibility is to implement the needed steps of the algorithm i.e., the ones declared abstract in the parent class.

parseFile, writeUpdatedDocument are abstract methods - to be filled in by the subclass

validateChecksum, updateDocument - concrete implementation part of the base/abstract class (The subclass can call this if needed).

The concrete methods part of the superclass have been marked as final. This is to prevent the subclasses from overriding them. Let us see this in action

public class Main {
    public static void main(String[] args) {
        System.out.println("---Processing the XML file---");
        DocManipulator docManipulator = new XmlDocumentManipulator();
        docManipulator.processFile(new File("..."));

        System.out.println();
        System.out.println("---Processing the JSON file---");
        docManipulator = new JsonDocumentManipulator();
        docManipulator.processFile(new File("..."));
    }
}

Outputs:

---Processing the XML file---
Validating the file's checksum
Parsing file as an XMLDocument
Updating the document
Writing XML document
---Processing the JSON file---
Validating the file's checksum
Parsing file as an JSONDocument
Updating the document
Writing JSON document

Advantages of the Template Method Pattern

  1. Code duplication removed: Earlier, there was a lot of code duplication between the classes. The algorithm steps and common methods were duplicated. Now, they are in the base class that controls everything.

  2. Algorithm has been encapsulated: In the first version, if the algorithm is to change in the future, then we have to make the change in each class (due to the duplicated code). Now, the base class is responsible for the algorithm and there is just one place that needs to change.

  3. Easier to add a new subclass: To add a new subclass, we just have to implement the needed methods (the abstract methods). Compare this to the old code where we have to copy-paste the entire code and change the needed parts. Thus, the template pattern provides a framework to plug in new subclasses easily; in our example, it enables easier plugging in of new file types/formats.

Template method pattern and the Hollywood principle

The Template method pattern follows a famous principle called the Hollywood principle. It states “Don’t call us, we will call you”. Its general philosophy is that a low-level component should not call into a high-level component.

With the template method pattern, we established just that. None of the subclasses (low-level components) need to call the template method (high-level component). The high-level component is the base class that has the template method. The clients depend on the abstract base class only. The subclasses only provide the required steps of the algorithm implementation. Imagine the role of the subclasses as to fill in the missing pieces of a jigsaw puzzle. Laying out the initial structure of the puzzle is the responsibility of the class that has the template method (the base class) and the subclasses aren’t aware of it.

Structure

TemplateMethodDesignPattern

Participants

AbstractClass (DocManipulator)

  • It defines abstract primitive operations that the concrete subclasses will implement i.e., specific steps of an algorithm.
  • Implements a template method defining the skeleton of an algorithm. The template method calls the primitive operations as well as the methods defined in the AbstractClass or those of other objects.

ConcreteClass (XmlDocumentManipulator, JsonDocumentManipulator)

  • Implements the primitive operations to carry out subclass-specific steps of the algorithm.

Using generics to the base class

Before I wrap up this post, I want to show a small change to the base class. If you looked at the template method implementation vs the first version of the code seen at the start of this post, there is a difference in the type of the document passed to the writeUpdatedDocument method. In the first version, the writeUpdatedDocument method received a concrete type of Document (XmlDocument/JsonDocument). But in the template pattern implementation, since it was moved as an abstract method to the base class, it has to be made as Document.

The subclass, if it has to operate on a concrete Document type, needs to typecast it. A better solution is to generify the base class with a type <T>. Here is how it will look

public abstract class DocManipulator<T extends Document> {
    public void processFile(File file) {
        validateChecksum(file);
        T document = parseFile(file);
        updateDocument(document);
        writeUpdatedDocument(document, file.getPath());
    }

    private final void validateChecksum(File input) {
        System.out.println("Validating the file's checksum");
    }

    private final void updateDocument(Document document) {
        System.out.println("Updating the document");
    }

    protected abstract T parseFile(File input);
    protected abstract void writeUpdatedDocument(T document, String path);
}
public class JsonDocumentManipulator extends DocManipulator<JsonDocument> {
    @Override
    protected JsonDocument parseFile(File input) {
        System.out.println("Parsing file as an JSONDocument");
        return new JsonDocument();
    }

    @Override
    protected void writeUpdatedDocument(JsonDocument document, String path) {
        System.out.println("Writing JSON document");
    }
}
public class XmlDocumentManipulator extends DocManipulator<XmlDocument> {
    @Override
    protected XmlDocument parseFile(File input) {
        System.out.println("Parsing file as an XMLDocument");
        return new XmlDocument();
    }

    @Override
    protected void writeUpdatedDocument(XmlDocument document, String path) {
        System.out.println("Writing XML document");
    }
}

The base class now implements DocManipulator<JsonDocument> and DocManipulator<XmlDocument>. The writeUpdatedDocument method gets the same type as the type parameter.

Conclusion

In this post, we learnt about the template method design pattern. The Template Method Design Pattern defines the steps of an algorithm and allows subclasses to provide the implementation for one or more steps. We also looked at a file processing application example and applied the template design pattern to it.

Other related design patterns are - Strategy design pattern and the Factory Method Pattern.

References