Learn how to implement the Template Method design pattern in JavaScript, using classes and prototypes to define template methods and override abstract methods in subclasses.
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a method, deferring some steps to subclasses. This pattern lets subclasses redefine certain steps of an algorithm without changing its structure. In this section, we will explore how to implement the Template Method pattern in JavaScript, using both classes and prototypes to define template methods and override abstract methods in subclasses.
Before diving into the implementation, let’s clarify the core concepts of the Template Method pattern:
JavaScript does not have built-in support for abstract classes or methods, but we can simulate this behavior using classes and prototypes. We’ll start by implementing the pattern using ES6 classes and then explore how to achieve the same using prototypes.
Let’s consider a scenario where we have a DataProcessor
class that defines a template method for processing data. Subclasses like CSVDataProcessor
and JSONDataProcessor
will implement specific steps of the algorithm.
// Base class with the template method
class DataProcessor {
// Template method
process() {
this.readData();
this.processData();
this.saveData();
}
// Abstract methods
readData() {
throw new Error('readData() must be implemented');
}
processData() {
throw new Error('processData() must be implemented');
}
saveData() {
throw new Error('saveData() must be implemented');
}
}
// Subclass for processing CSV data
class CSVDataProcessor extends DataProcessor {
readData() {
console.log('Reading CSV data...');
// Logic to read CSV data
}
processData() {
console.log('Processing CSV data...');
// Logic to process CSV data
}
saveData() {
console.log('Saving CSV data...');
// Logic to save CSV data
}
}
// Subclass for processing JSON data
class JSONDataProcessor extends DataProcessor {
readData() {
console.log('Reading JSON data...');
// Logic to read JSON data
}
processData() {
console.log('Processing JSON data...');
// Logic to process JSON data
}
saveData() {
console.log('Saving JSON data...');
// Logic to save JSON data
}
}
// Usage
const csvProcessor = new CSVDataProcessor();
csvProcessor.process();
const jsonProcessor = new JSONDataProcessor();
jsonProcessor.process();
In this example, the DataProcessor
class defines the process
method, which serves as the template method. It calls three abstract methods: readData
, processData
, and saveData
. These methods are implemented in the subclasses CSVDataProcessor
and JSONDataProcessor
.
JavaScript’s prototype-based inheritance can also be used to implement the Template Method pattern. This approach is more traditional in JavaScript, especially before the introduction of ES6 classes.
// Base class constructor
function DataProcessor() {}
// Template method
DataProcessor.prototype.process = function() {
this.readData();
this.processData();
this.saveData();
};
// Abstract methods
DataProcessor.prototype.readData = function() {
throw new Error('readData() must be implemented');
};
DataProcessor.prototype.processData = function() {
throw new Error('processData() must be implemented');
};
DataProcessor.prototype.saveData = function() {
throw new Error('saveData() must be implemented');
};
// Subclass for processing CSV data
function CSVDataProcessor() {}
CSVDataProcessor.prototype = Object.create(DataProcessor.prototype);
CSVDataProcessor.prototype.constructor = CSVDataProcessor;
CSVDataProcessor.prototype.readData = function() {
console.log('Reading CSV data...');
// Logic to read CSV data
};
CSVDataProcessor.prototype.processData = function() {
console.log('Processing CSV data...');
// Logic to process CSV data
};
CSVDataProcessor.prototype.saveData = function() {
console.log('Saving CSV data...');
// Logic to save CSV data
};
// Subclass for processing JSON data
function JSONDataProcessor() {}
JSONDataProcessor.prototype = Object.create(DataProcessor.prototype);
JSONDataProcessor.prototype.constructor = JSONDataProcessor;
JSONDataProcessor.prototype.readData = function() {
console.log('Reading JSON data...');
// Logic to read JSON data
};
JSONDataProcessor.prototype.processData = function() {
console.log('Processing JSON data...');
// Logic to process JSON data
};
JSONDataProcessor.prototype.saveData = function() {
console.log('Saving JSON data...');
// Logic to save JSON data
};
// Usage
var csvProcessor = new CSVDataProcessor();
csvProcessor.process();
var jsonProcessor = new JSONDataProcessor();
jsonProcessor.process();
In this prototype-based implementation, we define the template method process
on the DataProcessor
prototype. The abstract methods are also defined on the prototype, and subclasses like CSVDataProcessor
and JSONDataProcessor
override these methods with their specific implementations.
The Template Method pattern enforces the algorithm’s structure by defining the sequence of method calls in the template method. This ensures that the overall process remains consistent, while allowing subclasses to customize specific steps.
JavaScript’s lack of built-in abstract classes requires us to simulate abstract methods by throwing errors in the base class. This approach ensures that subclasses must implement these methods, maintaining the integrity of the pattern.
To better understand the flow of the Template Method pattern, let’s visualize the interaction between the base class and subclasses using a sequence diagram.
sequenceDiagram participant BaseClass as DataProcessor participant Subclass1 as CSVDataProcessor participant Subclass2 as JSONDataProcessor BaseClass->>Subclass1: process() Subclass1->>Subclass1: readData() Subclass1->>Subclass1: processData() Subclass1->>Subclass1: saveData() BaseClass->>Subclass2: process() Subclass2->>Subclass2: readData() Subclass2->>Subclass2: processData() Subclass2->>Subclass2: saveData()
This diagram illustrates how the process
method in the base class (DataProcessor
) calls the overridden methods in the subclasses (CSVDataProcessor
and JSONDataProcessor
).
To deepen your understanding of the Template Method pattern, try modifying the code examples:
Add a New Subclass: Create a new subclass for processing XML data. Implement the readData
, processData
, and saveData
methods with XML-specific logic.
Extend the Template Method: Add a new step to the template method, such as validateData
, and implement it in the subclasses.
Experiment with Error Handling: Modify the abstract methods to include error handling logic, ensuring that subclasses handle errors appropriately.
For more information on the Template Method pattern and other design patterns, consider exploring the following resources:
Remember, mastering design patterns like the Template Method pattern is a journey. As you continue to explore and experiment with these patterns, you’ll gain a deeper understanding of how to create flexible and maintainable code. Keep experimenting, stay curious, and enjoy the journey!