Template Method

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

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.

javascript
	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.