Template Method design pattern defines the steps of an algorithm and the order in which they should be performed. We need this when we have multiple forms of the same algorithm with minor variations implemented in different places.
In software DRY (don’t repeat yourself) is an important concept. Repetition can be of code, data or a pattern. In this case, it is of a pattern which we eliminate by abstracting out the commonalities into a template that goes into a abstract super-class and let the sub-classes extend the template and customize it their way.
Note the abstract super-class only creates a template and doesn’t how to implement all the steps.
For example, consider an issue tracking system where a bug goes through a cycle of state changes. For example, when a qa creates a bug, it first gets assigned to a module leader. Module leader reviews it and assigns it to a developer. Developer imparts the fix, resolves the issue. Issue gets assigned to a QA. QA tests the bug and either closes it or reopens it.
We can easily identify the various tasks involved in an issue tracking system like ‘Create New Issue’, ‘Assign Issue’, ‘Resolve Issue’, ‘Close Issue’/’Reopen Issue’.
Each task involves some validations, state changes in the bug and notifications that are sent to the parties interested in keeping a watch on the issue.
Since all the tasks follow similar algorithm, instead of letting the sub-classes manage the flow, the control is inverted so that the flow is driven by abstract super-classes. The subclass’s concrete methods chip in whenever called in the flow of the algorithm.
You can notice that AbstractTask handles the main algorithm which is called through method execute().
It is better to define the types in interfaces rather than than classes.
In our example, the execute() method is in the interface Task and the abstract super-class AbstractTask implements Task. It implements execute but the individual steps are protected methods in AbstractTask.
package javarticles.templatepattern; public interface Task { void execute(); }
Always make sure abstract parent class implements an interface. You can see below AbstractTask implements Task.
package javarticles.templatepattern; public abstract class AbstractTask implements Task { @Override public final void execute() { validateTask(); updateTaskState(); notifyInterestedParties(); } protected abstract void validateTask(); protected abstract void updateTaskState(); protected abstract void notifyInterestedParties(); }
How should we design the abstract super-class?
Idea is to encapsulate the individual steps into a final method and provide abstract methods for steps which the abstract class doesn’t know how to implement and let concrete classes of this abstract super-class implement it. The workflow is controlled by the abstract super-class as it knows exactly the steps and the order in which they have to be performed. The methods to be implemented are abstract and protected so that the concrete classes are forced to override the methods.
I have provided here few sub-classes. You can download the entire code here templatepattern.zip.
NewBugTask
package javarticles.templatepattern; public class NewBugTask extends AbstractTask { private Issue issue; public NewBugTask(Issue issue) { this.issue = issue; } @Override protected void validateTask() { System.out.println("NewBug: validate if a module leader exists for module " + issue.getModuleLeader().getName()); TeamMember moduleLeader = issue.getModuleLeader(); if (moduleLeader == null) { throw new RuntimeException("No moduler leader assigned for module <"); } } @Override protected void updateTaskState() { System.out.println("NewBug: Create new issue and assign to module leader " + issue.getModuleLeader().getName()); this.issue.setStatus(StatusEnum.NEW); TeamMember moduleLeader = issue.getModuleLeader(); moduleLeader.addToRoadmap(issue); issue.setAssignedTo(moduleLeader); } @Override protected void notifyInterestedParties() { System.out.println("NewBug: notify interested parties"); issue.getAssignedTo().notifyMember(); issue.getProject().notifyProjectLead(); issue.getReportedBy().notifyMember(); issue.getProject().notifyQaLead(); } }
ResolveBugTask
package javarticles.templatepattern; public class ResolveBugTask extends AbstractTask { private Issue issue; public ResolveBugTask(Issue issue) { this.issue = issue; this.issue.setStatus(StatusEnum.RESOLVED); } @Override protected void validateTask() { System.out.println("ResolveBug: validate if the code is reviewed"); if (issue.getIsCodeReviewRqd() && issue.getIsCodeReviewDone()) { throw new RuntimeException("Code review required"); } } @Override protected void updateTaskState() { System.out.println("ResolveBug: resolve issue"); issue.getDev().removeFromRoadmap(issue); System.out.println("ResolveBug: issue resolved, removed from dev " + issue.getDev().getName() + " roadmap"); TeamMember qa = issue.getQa(); qa.addToRoadmap(issue); issue.setAssignedTo(qa); System.out.println("ResolveBug: assigned to qa " + issue.getQa().getName()); } @Override protected void notifyInterestedParties() { System.out.println("ResolveBug: notify interested parties"); issue.getDev().notifyMember(); issue.getQa().notifyMember(); issue.notifyInterestedParties(); } }
Run the example of Template Design Pattern
@Test public void issueFlow() { Project project = new Project(); Issue issue = project.createIssue("Example of template pattern", "Tom", "ModuleA"); issue.assignTo("Ram"); issue.resolve(); issue.close("Qa done"); }
Output
NewBug: validate if a module leader exists for module ModuleA NewBug: Create new issue and assign to module leader ModuleA NewBug: notify interested parties notify ModuleA notify project lead notify Tom notify qa lead AssignBug: validate whether dev Ram exists AssignBug: assign the bug to Ram AssignBug: notify interested parties notify Ram notify Tom notify others if any ResolveBug: validate if the code is reviewed ResolveBug: resolve issue ResolveBug: issue resolved, removed from dev Ram roadmap ResolveBug: assigned to qa Tom ResolveBug: notify interested parties notify Ram notify Tom notify others if any CloseBug: validate if qa notes exists CloseBug: close the issue CloseBug: remove from qa Tom roadmap CloseBug: notify interested parties notify Ram notify Tom notify others if any
Important Points on Template Design Pattern
- Use Template Method Pattern to capture an algorithm in an abstract super-class but defer implementations of individual steps to sub-classes.
- Abstract super-class should take the burden of writing tricky part. The methods that may change between sub-classes must be made abstract.
- Abstract super-classes are also often used to implement some, but not all, methods of an interface.
- The remaining methods which vary between concrete implementations are left unimplemented.
- This differs from Template Method Pattern in that abstract super-class doesn’t handle workflow.
Usages of Template Design Pattern
- All non-abstract methods of InputStream, OutputStream, Reader and Writer.
- All non-abstract methods of AbstractList, AbstractSet and AbstractMap.
- In servlet API, class HttpServlet implements template pattern, its methods like doGet(), doPost(), doPut() has the main algorithm. Sub-classes will tweak the behavior by overriding the abstract methods.
- In spring, classes like JdbcTemplate and JmsTemplate classes has methods like update(), query(), execute() etc. It encapsulates the common code like getting a Connection, creating a Session, closing the connection. Actual action is called through a callback method.
Download source code
In this article, I have shown you an example of Template Method Pattern.
You can download the entire code here templatepattern.zip