The algorithmic skeleton definition of the Template Method pattern.
Template Method Pattern's Algorithm Skeleton Definition
The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a parent class, allowing subclasses to override certain steps without changing the algorithm's structure. The core of this pattern lies in encapsulating the invariant parts in the parent class and delegating the variable parts to subclasses for implementation.
Pattern Structure and Roles
The Template Method pattern typically includes the following roles:
- Abstract Class: Defines the algorithm skeleton and basic operations
- Concrete Class: Implements the abstract operations in the abstract class
// Abstract Class
class AbstractClass {
// Template method, defines the algorithm skeleton
templateMethod() {
this.primitiveOperation1();
this.primitiveOperation2();
this.concreteOperation();
this.hook();
}
// Basic operations, must be implemented by subclasses
primitiveOperation1() {
throw new Error("Must implement primitiveOperation1 method");
}
primitiveOperation2() {
throw new Error("Must implement primitiveOperation2 method");
}
// Concrete operation, already implemented
concreteOperation() {
console.log("Executing concrete operation");
}
// Hook method, subclasses can optionally override
hook() {}
}
JavaScript Implementation Example
Below is a complete example of a real-world application scenario, demonstrating how to use the Template Method pattern to handle different types of document exports:
// Document Exporter Abstract Class
class DocumentExporter {
export() {
this.prepareData();
this.formatHeader();
this.formatContent();
this.formatFooter();
if (this.needCompression()) {
this.compress();
}
this.save();
}
prepareData() {
console.log("Preparing export data...");
}
formatHeader() {
throw new Error("Must implement formatHeader method");
}
formatContent() {
throw new Error("Must implement formatContent method");
}
formatFooter() {
throw new Error("Must implement formatFooter method");
}
// Hook method, defaults to no compression
needCompression() {
return false;
}
compress() {
console.log("Compressing document...");
}
save() {
console.log("Saving document to disk...");
}
}
// PDF Export Implementation
class PDFExporter extends DocumentExporter {
formatHeader() {
console.log("Generating PDF header...");
}
formatContent() {
console.log("Converting content to PDF format...");
}
formatFooter() {
console.log("Adding PDF footer and page numbers...");
}
}
// CSV Export Implementation
class CSVExporter extends DocumentExporter {
formatHeader() {
console.log("Generating CSV header row...");
}
formatContent() {
console.log("Converting data to CSV format...");
}
formatFooter() {
console.log("Adding CSV end marker...");
}
// Override hook method
needCompression() {
return true;
}
}
// Usage Example
const pdfExporter = new PDFExporter();
pdfExporter.export();
const csvExporter = new CSVExporter();
csvExporter.export();
Pattern Characteristics and Advantages
The Template Method pattern has the following notable characteristics:
- Encapsulates invariant parts: Moves invariant behaviors of the algorithm to the parent class, eliminating duplicate code in subclasses
- Extends variable parts: Allows subclasses to implement variable parts, making it easy to extend new behaviors
- Inverted control structure: The parent class calls operations of the subclass, not the other way around
- Complies with the Open/Closed Principle: Adding new subclasses does not require modifying the parent class code
Application of Hook Methods
Hook methods are optional operations that subclasses can choose to override. Hooks typically have default implementations, allowing subclasses to selectively "hook" into specific points of the algorithm:
class GameAI {
// Template method
turn() {
this.collectResources();
this.buildStructures();
if (this.shouldBuildUnits()) {
this.buildUnits();
}
this.attack();
}
collectResources() {
// Default implementation
}
buildStructures() {
// Must implement
throw new Error("Must implement buildStructures method");
}
// Hook method
shouldBuildUnits() {
return true;
}
buildUnits() {
// Must implement
throw new Error("Must implement buildUnits method");
}
attack() {
// Must implement
throw new Error("Must implement attack method");
}
}
// Concrete AI Implementation
class MonsterAI extends GameAI {
buildStructures() {
console.log("Monsters do not build structures");
}
buildUnits() {
console.log("Spawning monster units");
}
attack() {
console.log("Monsters attack players");
}
// Override hook method
shouldBuildUnits() {
return Math.random() > 0.5;
}
}
Practical Application Scenarios
The Template Method pattern has wide applications in front-end development:
- Framework lifecycles: Such as React component lifecycle methods
- Data processing flows: Such as standard processes for data validation, transformation, and saving
- UI rendering: Such as pre-render preparation, main rendering, and post-render processing
- Test cases: Such as test preparation, test execution, and post-test cleanup
Below is an example of front-end form validation:
class FormValidator {
validate(formData) {
this.beforeValidation();
const errors = {};
for (const field of this.getFieldsToValidate()) {
const value = formData[field];
const fieldErrors = this.validateField(field, value);
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
}
}
this.afterValidation(errors);
return errors;
}
beforeValidation() {
console.log("Starting validation...");
}
getFieldsToValidate() {
throw new Error("Must implement getFieldsToValidate method");
}
validateField(field, value) {
throw new Error("Must implement validateField method");
}
afterValidation(errors) {
console.log("Validation completed, errors found:", Object.keys(errors).length);
}
}
class LoginFormValidator extends FormValidator {
getFieldsToValidate() {
return ['username', 'password'];
}
validateField(field, value) {
const errors = [];
if (field === 'username') {
if (!value) errors.push("Username cannot be empty");
if (value && value.length < 6) errors.push("Username must be at least 6 characters");
}
if (field === 'password') {
if (!value) errors.push("Password cannot be empty");
if (value && value.length < 8) errors.push("Password must be at least 8 characters");
}
return errors;
}
}
// Usage Example
const validator = new LoginFormValidator();
const errors = validator.validate({
username: 'admin',
password: '123'
});
console.log(errors);
Comparison with the Strategy Pattern
Both the Template Method pattern and the Strategy pattern are used to encapsulate algorithms, but they differ in the following ways:
- Inheritance vs Composition: Template Method uses inheritance, while Strategy uses composition
- Algorithm Integrity: Template Method maintains the algorithm structure, while Strategy completely replaces the algorithm
- Runtime Changes: Strategy can switch algorithms at runtime, while Template Method is determined at compile time
// Strategy Pattern Implementation Comparison
class ValidatorStrategy {
constructor(strategy) {
this.strategy = strategy;
}
validate(formData) {
return this.strategy.validate(formData);
}
}
const loginStrategy = {
validate(formData) {
const errors = {};
// Validation logic...
return errors;
}
};
const validator = new ValidatorStrategy(loginStrategy);
validator.validate({/*...*/});
Pattern Variants and Extensions
The Template Method pattern can be combined with other patterns to form more powerful solutions:
- Factory Method: Certain steps in the template method can use factory methods to create objects
- Observer Pattern: Trigger events at key points in the algorithm to notify observers
- Decorator Pattern: Dynamically add additional behaviors to certain steps of the template method
// Example Combining with Observer Pattern
class ObservableDocumentExporter extends DocumentExporter {
constructor() {
super();
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
notify(event) {
this.observers.forEach(observer => observer.update(event));
}
export() {
this.notify('exportStarted');
super.export();
this.notify('exportCompleted');
}
}
class ExportLogger {
update(event) {
console.log(`Export event: ${event}`);
}
}
const exporter = new ObservableDocumentExporter();
exporter.addObserver(new ExportLogger());
exporter.export();
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn