Template Method Pattern
Introduction
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm’s structure.
Purpose
- To define the outline of an algorithm, allowing subclasses to implement the specific behavior of the algorithm steps.
- To let subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
Use Cases
- When a common behavior among subclasses should be centralized into a common class to avoid code duplication.
- In scenarios where subclasses should be able to extend the base algorithm without changing its structure.
Advantages/Disadvantages
Advantages:
- Facilitates code reuse and avoids duplication in subclasses.
- Provides a clear template for algorithms.
- Offers a way to enforce a step-by-step algorithm structure.
Disadvantages:
- Can be less flexible if subclasses need a different algorithm structure.
- Subclasses have to conform to the template structure defined by the base class.
Implementation in JavaScript
class AbstractClass {
templateMethod() {
this.baseOperation1();
this.requiredOperation1();
this.baseOperation2();
this.hook1();
this.requiredOperation2();
this.baseOperation3();
this.hook2();
}
baseOperation1() {
console.log('AbstractClass says: I am doing the bulk of the work');
}
baseOperation2() {}
baseOperation3() {}
requiredOperation1() {}
requiredOperation2() {}
hook1() {}
hook2() {}
}
class ConcreteClass1 extends AbstractClass {
requiredOperation1() {
console.log('ConcreteClass1 says: Implemented Operation1');
}
requiredOperation2() {
console.log('ConcreteClass1 says: Implemented Operation2');
}
}
class ConcreteClass2 extends AbstractClass {
requiredOperation1() {
console.log('ConcreteClass2 says: Implemented Operation1');
}
requiredOperation2() {
console.log('ConcreteClass2 says: Implemented Operation2');
}
hook1() {
console.log('ConcreteClass2 says: Overridden Hook1');
}
}
// Usage
const concreteClass1 = new ConcreteClass1();
const concreteClass2 = new ConcreteClass2();
console.log('Same client code can work with different subclasses:');
concreteClass1.templateMethod();
console.log('');
concreteClass2.templateMethod();
Practical Example
Imagine a backend scenario where you’re implementing a data processing pipeline. The steps in this pipeline (like fetching data, processing data, and saving results) are mostly the same, but some steps can differ based on the data source.
class DataProcessingPipeline {
process() {
this.fetchData();
this.parseData();
this.transformData();
this.saveData();
}
fetchData() {
console.log('Fetching data');
}
parseData() {
console.log('Parsing data');
}
transformData() {
// To be overridden by subclasses
}
saveData() {
console.log('Saving data');
}
}
class CSVDataProcessingPipeline extends DataProcessingPipeline {
transformData() {
console.log('Transforming CSV data');
}
}
class JSONDataProcessingPipeline extends DataProcessingPipeline {
transformData() {
console.log('Transforming JSON data');
}
}
// Usage
const csvPipeline = new CSVDataProcessingPipeline();
const jsonPipeline = new JSONDataProcessingPipeline();
csvPipeline.process(); // Processing steps including transforming CSV data
jsonPipeline.process(); // Processing steps including transforming JSON data
In this example, DataProcessingPipeline
provides the template method process()
which outlines the steps for data processing. Subclasses like CSVDataProcessingPipeline
and JSONDataProcessingPipeline
provide specific implementations for the transformData()
step.