Template Method Design Pattern (Behavioral)
Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
Problem
Section titled βProblemβImagine youβre creating a data mining application that analyzes corporate documents. Users feed the app documents in various formats (PDF, DOC, CSV), and it tries to extract meaningful data from these files in a uniform format.
The first version of the app could work only with DOC files. In the second version, it could support CSV files. A month later, you βtaughtβ it to extract data from PDF files.
At some point, you noticed that all three classes have a lot of similar code. While the code for dealing with various data formats was entirely different in all classes, the code for data processing and analysis was almost identical. Wouldnβt it be great to get rid of the code duplication, leaving the algorithm structure intact?.
Solution
Section titled βSolutionβThe Template Method pattern suggests that you break down an algorithm into a series of steps, turn these steps into methods, and put a series of calls to these methods inside a single template method. The steps may either be abstract, or have some default implementation.
To use the algorithm, the client is supposed to provide its own subclass, implement all abstract steps, and override some of the optional ones if needed (but not the template method itself).
Real-World Analogy
Section titled βReal-World AnalogyβThe template method approach can be used in mass housing construction. An architectural plan for building a standard house may contain several extension points that let a potential owner adjust some details of the resulting house.
Each building step, such as laying the foundation, framing, and building walls, remains the same. However, the builders can change the plumbing, the wiring, or the paint of the walls to make the house slightly different from others.
Structure
Section titled βStructureβ- Abstract Class declares methods that act as steps of an algorithm, as well as the actual template method which calls these steps in a specific order.
- Concrete Classes can override all of the steps, but not the template method itself.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class AbstractClass {
...
+templateMethod()
+step1()
+step2()*
+step3()*
+hook1()
}
class ConcreteClass1 {
+step2()
+step3()
}
class ConcreteClass2 {
+step2()
+step3()
+hook1()
}
AbstractClass <|-- ConcreteClass1
AbstractClass <|-- ConcreteClass2
Pseudocode
Section titled βPseudocodeβ# The abstract class defines a template method that contains a# skeleton of some algorithm.class GameAI: # The template method defines the skeleton of an algorithm. method turn(): collectResources() buildStructures() buildUnits() attack()
# Some of the steps may be implemented right in a base class. method collectResources(): foreach (s in structures): s.collect()
# And some of them may be defined as abstract. abstract method buildStructures() abstract method buildUnits()
method attack(): enemy = closestEnemy() if (enemy == null): sendScouts(map.center) else: sendWarriors(enemy.position)
# Concrete classes provide implementation for abstract steps.class OrcsAI extends GameAI: method buildStructures(): if (resources > 100): build("OrcHut")
method buildUnits(): if (resources > 50): addUnitToQueue("Grunt")
class MonstersAI extends GameAI: method buildStructures(): # Monsters don't build structures.
method buildUnits(): # Monsters don't build units.Applicability
Section titled βApplicabilityβ- Varying specific steps: Use the Template Method pattern when you want to let clients extend only particular steps of an algorithm, but not the whole algorithm or its structure.
- Eliminating code duplication: Use the pattern when you have several classes that contain almost identical algorithms with some minor differences.
How to Implement
Section titled βHow to Implementβ- Analyze the target algorithm to see if you can break it into steps.
- Create the abstract base class and declare the template method and a set of abstract methods representing the algorithmβs steps.
- Itβs okay if some steps have a default implementation.
- Think about adding hooks between the crucial steps of the algorithm.
- For each variation of the algorithm, create a new concrete subclass.
Pros and Cons
Section titled βPros and ConsβPros:
- You can let clients override only certain parts of a large algorithm, making them less affected by changes that happen to other parts.
- You can pull the duplicate code into a superclass.
Cons:
- Some clients may be limited by the provided skeleton of an algorithm.
- You might violate the Liskov Substitution Principle by suppressing a default step implementation via a subclass.
- Template methods tend to be harder to maintain the more steps they have.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- Factory Method is a specialization of Template Method. At the same time, a Factory Method may serve as a step in a large Template Method.
- Template Method is based on inheritance: it lets you alter parts of an algorithm by extending those parts in subclasses. Strategy is based on composition: you can alter parts of the objectβs behavior by supplying it with different strategies that correspond to that behavior.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Templatepublic abstract class DataProcessor{ public void Process() { ReadData(); ProcessData(); SaveData(); }
protected abstract void ReadData(); protected abstract void ProcessData(); protected abstract void SaveData();}
// Concrete Implementationspublic class CsvProcessor : DataProcessor{ protected override void ReadData() => Console.WriteLine("Read CSV"); protected override void ProcessData() => Console.WriteLine("Process CSV"); protected override void SaveData() => Console.WriteLine("Save CSV");}
public class JsonProcessor : DataProcessor{ protected override void ReadData() => Console.WriteLine("Read JSON"); protected override void ProcessData() => Console.WriteLine("Process JSON"); protected override void SaveData() => Console.WriteLine("Save JSON");}
// Usageclass Program{ static void Main() { DataProcessor csv = new CsvProcessor(); csv.Process();
DataProcessor json = new JsonProcessor(); json.Process(); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom abc import ABC, abstractmethod
class AbstractClass(ABC): """ The Abstract Class defines a template method that contains a skeleton of some algorithm, composed of calls to (usually) abstract primitive operations. """ def template_method(self) -> None: self.base_operation1() self.required_operations1() self.base_operation2() self.hook1() self.required_operation2() self.base_operation3() self.hook2()
def base_operation1(self) -> None: print("AbstractClass says: I am doing the bulk of the work")
def base_operation2(self) -> None: print("AbstractClass says: But I let subclasses override some operations")
def base_operation3(self) -> None: print("AbstractClass says: But I am am doing the bulk of the work anyway")
@abstractmethod def required_operations1(self) -> None: pass
@abstractmethod def required_operation2(self) -> None: pass
def hook1(self) -> None: pass
def hook2(self) -> None: pass
class ConcreteClass1(AbstractClass): def required_operations1(self) -> None: print("ConcreteClass1 says: Implemented Operation1")
def required_operation2(self) -> None: print("ConcreteClass1 says: Implemented Operation2")
class ConcreteClass2(AbstractClass): def required_operations1(self) -> None: print("ConcreteClass2 says: Implemented Operation1")
def required_operation2(self) -> None: print("ConcreteClass2 says: Implemented Operation2")
def hook1(self) -> None: print("ConcreteClass2 says: Overridden Hook1")
if __name__ == "__main__": print("Same client code can work with different subclasses:") ConcreteClass1().template_method() print("\n") ConcreteClass2().template_method()Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>
// Templateclass DataProcessor {public: void Process() { ReadData(); ProcessData(); SaveData(); }
protected: virtual void ReadData() = 0; virtual void ProcessData() = 0; virtual void SaveData() = 0;};
// Concrete Implementationsclass CsvProcessor : public DataProcessor {protected: void ReadData() override { std::cout << "Read CSV" << std::endl; }
void ProcessData() override { std::cout << "Process CSV" << std::endl; }
void SaveData() override { std::cout << "Save CSV" << std::endl; }};
class JsonProcessor : public DataProcessor {protected: void ReadData() override { std::cout << "Read JSON" << std::endl; }
void ProcessData() override { std::cout << "Process JSON" << std::endl; }
void SaveData() override { std::cout << "Save JSON" << std::endl; }};
// Usageint main() { DataProcessor* csv = new CsvProcessor(); csv->Process();
DataProcessor* json = new JsonProcessor(); json->Process();
delete csv; delete json;
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Template (Abstract Class)public abstract class DataProcessor {
public void process() { readData(); processData(); saveData(); }
protected abstract void readData(); protected abstract void processData(); protected abstract void saveData();}
// Concrete Implementationspublic class CsvProcessor extends DataProcessor {
@Override protected void readData() { System.out.println("Read CSV"); }
@Override protected void processData() { System.out.println("Process CSV"); }
@Override protected void saveData() { System.out.println("Save CSV"); }}
public class JsonProcessor extends DataProcessor {
@Override protected void readData() { System.out.println("Read JSON"); }
@Override protected void processData() { System.out.println("Process JSON"); }
@Override protected void saveData() { System.out.println("Save JSON"); }}
// Usagepublic class Program { public static void main(String[] args) { DataProcessor csv = new CsvProcessor(); csv.process();
DataProcessor json = new JsonProcessor(); json.process(); }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Template (abstract class)class DataProcessor { constructor() { if (new.target === DataProcessor) { throw new Error("DataProcessor is abstract and cannot be instantiated directly"); } }
process() { this.readData(); this.processData(); this.saveData(); }
readData() { throw new Error("readData() must be implemented"); }
processData() { throw new Error("processData() must be implemented"); }
saveData() { throw new Error("saveData() must be implemented"); }}
// Concrete Implementationsclass CsvProcessor extends DataProcessor { readData() { console.log("Read CSV"); }
processData() { console.log("Process CSV"); }
saveData() { console.log("Save CSV"); }}
class JsonProcessor extends DataProcessor { readData() { console.log("Read JSON"); }
processData() { console.log("Process JSON"); }
saveData() { console.log("Save JSON"); }}
// Usageconst csv = new CsvProcessor();csv.process();
const json = new JsonProcessor();json.process();Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Template' ============================Public MustInherit Class DataProcessor Public Sub Process() ReadData() ProcessData() SaveData() End Sub
Protected MustOverride Sub ReadData() Protected MustOverride Sub ProcessData() Protected MustOverride Sub SaveData()End Class
' ============================' Concrete Implementations' ============================Public Class CsvProcessor Inherits DataProcessor
Protected Overrides Sub ReadData() Console.WriteLine("Read CSV") End Sub
Protected Overrides Sub ProcessData() Console.WriteLine("Process CSV") End Sub
Protected Overrides Sub SaveData() Console.WriteLine("Save CSV") End SubEnd Class
Public Class JsonProcessor Inherits DataProcessor
Protected Overrides Sub ReadData() Console.WriteLine("Read JSON") End Sub
Protected Overrides Sub ProcessData() Console.WriteLine("Process JSON") End Sub
Protected Overrides Sub SaveData() Console.WriteLine("Save JSON") End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim csv As DataProcessor = New CsvProcessor() csv.Process()
Dim json As DataProcessor = New JsonProcessor() json.Process() End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Templatetrait DataProcessor { fn process(&self) { self.read_data(); self.process_data(); self.save_data(); }
fn read_data(&self); fn process_data(&self); fn save_data(&self);}
// Concrete Implementationsstruct CsvProcessor;
impl DataProcessor for CsvProcessor { fn read_data(&self) { println!("Read CSV"); }
fn process_data(&self) { println!("Process CSV"); }
fn save_data(&self) { println!("Save CSV"); }}
struct JsonProcessor;
impl DataProcessor for JsonProcessor { fn read_data(&self) { println!("Read JSON"); }
fn process_data(&self) { println!("Process JSON"); }
fn save_data(&self) { println!("Save JSON"); }}
// Usagefn main() { let csv = CsvProcessor; csv.process();
let json = JsonProcessor; json.process();}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Templateabstract class DataProcessor{ public function process(): void { $this->readData(); $this->processData(); $this->saveData(); }
abstract protected function readData(): void; abstract protected function processData(): void; abstract protected function saveData(): void;}
// Concrete Implementationsclass CsvProcessor extends DataProcessor{ protected function readData(): void { echo "Read CSV\n"; }
protected function processData(): void { echo "Process CSV\n"; }
protected function saveData(): void { echo "Save CSV\n"; }}
class JsonProcessor extends DataProcessor{ protected function readData(): void { echo "Read JSON\n"; }
protected function processData(): void { echo "Process JSON\n"; }
protected function saveData(): void { echo "Save JSON\n"; }}
// Usage$csv = new CsvProcessor();$csv->process();
$json = new JsonProcessor();$json->process();Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Template (interface)type DataProcessor interface { Process() ReadData() ProcessData() SaveData()}
// Base struct providing the template methodtype BaseProcessor struct { DataProcessor}
func (bp *BaseProcessor) Process() { bp.ReadData() bp.ProcessData() bp.SaveData()}
// Concrete Implementationstype CsvProcessor struct { BaseProcessor}
func NewCsvProcessor() *CsvProcessor { p := &CsvProcessor{} p.BaseProcessor.DataProcessor = p return p}
func (CsvProcessor) ReadData() { fmt.Println("Read CSV") }func (CsvProcessor) ProcessData() { fmt.Println("Process CSV") }func (CsvProcessor) SaveData() { fmt.Println("Save CSV") }
type JsonProcessor struct { BaseProcessor}
func NewJsonProcessor() *JsonProcessor { p := &JsonProcessor{} p.BaseProcessor.DataProcessor = p return p}
func (JsonProcessor) ReadData() { fmt.Println("Read JSON") }func (JsonProcessor) ProcessData() { fmt.Println("Process JSON") }func (JsonProcessor) SaveData() { fmt.Println("Save JSON") }
// Usagefunc main() { csv := NewCsvProcessor() csv.Process()
json := NewJsonProcessor() json.Process()}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Templateclass DataProcessordef process read_data process_data save_dataend
def read_data raise NotImplementedError, "Subclasses must implement read_data"end
def process_data raise NotImplementedError, "Subclasses must implement process_data"end
def save_data raise NotImplementedError, "Subclasses must implement save_data"endend
# Concrete Implementationsclass CsvProcessor < DataProcessordef read_data puts "Read CSV"end
def process_data puts "Process CSV"end
def save_data puts "Save CSV"endend
class JsonProcessor < DataProcessordef read_data puts "Read JSON"end
def process_data puts "Process JSON"end
def save_data puts "Save JSON"endend
# Usagecsv = CsvProcessor.newcsv.process
json = JsonProcessor.newjson.processCode Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Template (Abstract Class)############################package DataProcessor;
sub new { bless {}, shift }
sub process { my ($self) = @_; $self->read_data(); $self->process_data(); $self->save_data();}
sub read_data { die "read_data() must be implemented by subclass" }sub process_data { die "process_data() must be implemented by subclass" }sub save_data { die "save_data() must be implemented by subclass" }
############################# Concrete Implementations############################package CsvProcessor;our @ISA = ('DataProcessor');
sub read_data { print "Read CSV\n" }sub process_data { print "Process CSV\n" }sub save_data { print "Save CSV\n" }
package JsonProcessor;our @ISA = ('DataProcessor');
sub read_data { print "Read JSON\n" }sub process_data { print "Process JSON\n" }sub save_data { print "Save JSON\n" }
############################# Usage############################package main;
my $csv = CsvProcessor->new();$csv->process();
my $json = JsonProcessor->new();$json->process();Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Template (abstract class)abstract class DataProcessor { process(): void { this.readData(); this.processData(); this.saveData(); }
protected abstract readData(): void; protected abstract processData(): void; protected abstract saveData(): void;}
// Concrete Implementationsclass CsvProcessor extends DataProcessor { protected readData(): void { console.log("Read CSV"); }
protected processData(): void { console.log("Process CSV"); }
protected saveData(): void { console.log("Save CSV"); }}
class JsonProcessor extends DataProcessor { protected readData(): void { console.log("Read JSON"); }
protected processData(): void { console.log("Process JSON"); }
protected saveData(): void { console.log("Save JSON"); }}
// Usageconst csv: DataProcessor = new CsvProcessor();csv.process();
const json: DataProcessor = new JsonProcessor();json.process();