Home/Design Patterns/Template Method Pattern

Template Method Pattern

🎯 Explain Like I'm 5...

Imagine your morning routine! Every morning you follow the same steps, but some details change each day. Let's see how this works:

📝 The Morning Routine Template:

  • 1. Wake up (everyone does this the same way)
  • 2. Brush teeth (everyone does this the same way)
  • 3. Eat breakfast (DIFFERENT for everyone!)
  • 4. Get dressed (DIFFERENT for everyone!)
  • 5. Go to school/work (everyone does this)

💡 The Template Method Magic!

  • The ORDER of steps is FIXED (the template!) 📋
  • Some steps are the SAME for everyone (wake up, brush teeth)
  • Some steps are DIFFERENT for each person:
  • • You might eat cereal 🥣
  • • Your sister might eat toast 🍞
  • • Your dad might eat eggs 🍳
  • But EVERYONE follows the same routine ORDER!

🌟 The Key Idea:

Template Method is like a RECIPE! The recipe tells you the STEPS (add flour, mix, bake) but YOU decide the DETAILS (chocolate chip or oatmeal cookies?). The skeleton stays the same, but you customize the parts!

🚀 Why Is This Pattern Useful?

  • Reuse the same algorithm structure in multiple places!
  • Let subclasses customize behavior without changing the overall flow!
  • Prevent code duplication - write common steps once!
  • Enforce a specific order of operations that can't be changed!

📋 Pattern Purpose

The Template Method pattern defines the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the algorithm's structure. It's a behavioral pattern that promotes code reuse and follows the Hollywood Principle: "Don't call us, we'll call you."

⚡ When to Use Template Method Pattern

  • You have multiple classes that implement similar algorithms with minor variations
  • You want to let subclasses extend only particular steps of an algorithm
  • You need to control the order of operations and prevent subclasses from changing it
  • You have common behavior that should be shared across multiple classes
  • You want to avoid code duplication in similar algorithms

🧩 Pattern Components

AbstractClass:

Defines the template method and declares abstract primitive operations

ConcreteClass:

Implements the primitive operations to carry out subclass-specific steps

Template Method:

Defines the skeleton of the algorithm, calling primitive operations in a specific order

Primitive Operations:

Abstract methods that subclasses must implement, or hooks that can be optionally overridden

💻 Java Implementations

Example 1: Beverage Template (Tea and Coffee)

Making different beverages with the same preparation steps but different ingredients.

Beverage.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Abstract Class - defines the template method
public abstract class Beverage {
// Template Method - defines the algorithm skeleton
// FINAL to prevent subclasses from changing the flow
public final void prepareBeverage() {
boilWater(); // Common step
brew(); // Varies by subclass
pourInCup(); // Common step
addCondiments(); // Varies by subclass
// Optional hook - subclasses can override if needed
if (customerWantsCondiments()) {
addExtraCondiments();
}
}
// Common step - same for all beverages
private void boilWater() {
System.out.println("Boiling water...");
}
// Common step - same for all beverages
private void pourInCup() {
System.out.println("Pouring into cup...");
}
// Abstract methods - subclasses MUST implement
protected abstract void brew();
protected abstract void addCondiments();
// Hook - optional, can be overridden
protected void addExtraCondiments() {
// Default: do nothing
}
// Hook - default implementation, can be overridden
protected boolean customerWantsCondiments() {
return true;
}
}
Tea.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Concrete Class 1 - Tea implementation
public class Tea extends Beverage {
@Override
protected void brew() {
System.out.println("Steeping the tea bag...");
}
@Override
protected void addCondiments() {
System.out.println("Adding lemon...");
}
@Override
protected void addExtraCondiments() {
System.out.println("Adding honey...");
}
}
Coffee.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Concrete Class 2 - Coffee implementation
public class Coffee extends Beverage {
@Override
protected void brew() {
System.out.println("Dripping coffee through filter...");
}
@Override
protected void addCondiments() {
System.out.println("Adding sugar and milk...");
}
@Override
protected boolean customerWantsCondiments() {
// Let's say coffee drinkers are picky
String answer = getUserInput();
return answer.toLowerCase().startsWith("y");
}
private String getUserInput() {
System.out.print("Would you like milk and sugar? (y/n): ");
// In real code, would read from console
return "y"; // Simplified for example
}
}
HotChocolate.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Concrete Class 3 - Hot Chocolate implementation
public class HotChocolate extends Beverage {
@Override
protected void brew() {
System.out.println("Mixing hot chocolate powder...");
}
@Override
protected void addCondiments() {
System.out.println("Adding whipped cream...");
}
@Override
protected void addExtraCondiments() {
System.out.println("Adding marshmallows and chocolate chips...");
}
}
BeverageDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Client code
public class BeverageDemo {
public static void main(String[] args) {
System.out.println("Making tea...");
Beverage tea = new Tea();
tea.prepareBeverage();
System.out.println("\n" + "=".repeat(30) + "\n");
System.out.println("Making coffee...");
Beverage coffee = new Coffee();
coffee.prepareBeverage();
System.out.println("\n" + "=".repeat(30) + "\n");
System.out.println("Making hot chocolate...");
Beverage hotChocolate = new HotChocolate();
hotChocolate.prepareBeverage();
}
}
/* OUTPUT:
Making tea...
Boiling water...
Steeping the tea bag...
Pouring into cup...
Adding lemon...
Adding honey...
==============================
Making coffee...
Boiling water...
Dripping coffee through filter...
Pouring into cup...
Would you like milk and sugar? (y/n): Adding sugar and milk...
==============================
Making hot chocolate...
Boiling water...
Mixing hot chocolate powder...
Pouring into cup...
Adding whipped cream...
Adding marshmallows and chocolate chips...
*/

Example 2: Data Processor Template (CSV and JSON)

Processing different data formats with the same algorithm structure.

DataProcessor.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Abstract Class - defines data processing template
public abstract class DataProcessor {
// Template Method - defines the algorithm structure
public final void processData(String filePath) {
openFile(filePath);
extractData();
parseData();
validateData();
if (needsTransformation()) {
transformData();
}
saveData();
closeFile();
System.out.println("Processing complete!\n");
}
// Common steps
private void openFile(String filePath) {
System.out.println("Opening file: " + filePath);
}
private void closeFile() {
System.out.println("Closing file...");
}
// Abstract methods - must be implemented
protected abstract void extractData();
protected abstract void parseData();
protected abstract void saveData();
// Hook with default implementation
protected void validateData() {
System.out.println("Validating data...");
}
// Hook with default implementation
protected void transformData() {
System.out.println("Transforming data...");
}
// Hook - can be overridden
protected boolean needsTransformation() {
return true;
}
}
CsvDataProcessor.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Concrete Class 1 - CSV processing
public class CsvDataProcessor extends DataProcessor {
@Override
protected void extractData() {
System.out.println("Extracting CSV data...");
}
@Override
protected void parseData() {
System.out.println("Parsing CSV: splitting by comma delimiter");
System.out.println("Creating data rows from CSV lines");
}
@Override
protected void validateData() {
System.out.println("Validating CSV: checking column count");
System.out.println("Validating CSV: verifying data types");
}
@Override
protected void transformData() {
System.out.println("Transforming CSV: trimming whitespace");
System.out.println("Transforming CSV: normalizing dates");
}
@Override
protected void saveData() {
System.out.println("Saving CSV data to database...");
}
}
JsonDataProcessor.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Concrete Class 2 - JSON processing
public class JsonDataProcessor extends DataProcessor {
@Override
protected void extractData() {
System.out.println("Extracting JSON data...");
}
@Override
protected void parseData() {
System.out.println("Parsing JSON: building object tree");
System.out.println("Creating data objects from JSON");
}
@Override
protected void validateData() {
System.out.println("Validating JSON: checking schema");
System.out.println("Validating JSON: verifying required fields");
}
@Override
protected boolean needsTransformation() {
// JSON is already structured, minimal transformation needed
return false;
}
@Override
protected void saveData() {
System.out.println("Saving JSON data to NoSQL database...");
}
}
XmlDataProcessor.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Concrete Class 3 - XML processing
public class XmlDataProcessor extends DataProcessor {
@Override
protected void extractData() {
System.out.println("Extracting XML data...");
}
@Override
protected void parseData() {
System.out.println("Parsing XML: building DOM tree");
System.out.println("Navigating XML nodes and attributes");
}
@Override
protected void validateData() {
System.out.println("Validating XML: checking against XSD schema");
System.out.println("Validating XML: verifying namespace");
}
@Override
protected void transformData() {
System.out.println("Transforming XML: applying XSLT");
System.out.println("Transforming XML: flattening nested structures");
}
@Override
protected void saveData() {
System.out.println("Saving XML data to SQL database...");
}
}
DataProcessorDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Client code
public class DataProcessorDemo {
public static void main(String[] args) {
System.out.println("=== Processing CSV File ===");
DataProcessor csvProcessor = new CsvDataProcessor();
csvProcessor.processData("data.csv");
System.out.println("=== Processing JSON File ===");
DataProcessor jsonProcessor = new JsonDataProcessor();
jsonProcessor.processData("data.json");
System.out.println("=== Processing XML File ===");
DataProcessor xmlProcessor = new XmlDataProcessor();
xmlProcessor.processData("data.xml");
}
}

Example 3: Game Template (Chess and Checkers)

Different games following the same game flow template.

Game.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Abstract Class - defines game flow template
public abstract class Game {
// Template Method - defines the game flow
public final void play() {
initializeGame();
startGame();
while (!isGameOver()) {
makePlay();
if (shouldShowStatus()) {
displayStatus();
}
}
endGame();
displayWinner();
}
// Abstract methods - must be implemented
protected abstract void initializeGame();
protected abstract void makePlay();
protected abstract boolean isGameOver();
protected abstract void displayWinner();
// Common method with default implementation
protected void startGame() {
System.out.println("Game started!");
}
// Common method with default implementation
protected void endGame() {
System.out.println("Game ended!");
}
// Hook - can be overridden
protected void displayStatus() {
System.out.println("Current game status...");
}
// Hook - can be overridden
protected boolean shouldShowStatus() {
return true;
}
}
Chess.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Concrete Class 1 - Chess game
public class Chess extends Game {
private int currentPlayer = 1;
private int movesCount = 0;
private static final int MAX_MOVES = 6; // Simplified
@Override
protected void initializeGame() {
System.out.println("Chess Game Initialized!");
System.out.println("Setting up chess board...");
System.out.println("Placing pieces in starting positions...");
currentPlayer = 1;
movesCount = 0;
}
@Override
protected void makePlay() {
movesCount++;
System.out.println("Player " + currentPlayer + " makes move #" + movesCount);
System.out.println("Moving piece...");
// Switch players
currentPlayer = (currentPlayer == 1) ? 2 : 1;
}
@Override
protected boolean isGameOver() {
if (movesCount >= MAX_MOVES) {
System.out.println("Checkmate! Game Over!");
return true;
}
return false;
}
@Override
protected void displayStatus() {
System.out.println("Chess Status: Move " + movesCount +
", Player " + currentPlayer + "'s turn");
}
@Override
protected void displayWinner() {
int winner = (currentPlayer == 1) ? 2 : 1;
System.out.println("Winner is Player " + winner + "!");
}
@Override
protected boolean shouldShowStatus() {
// Show status every 2 moves
return movesCount % 2 == 0;
}
}
Checkers.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Concrete Class 2 - Checkers game
public class Checkers extends Game {
private int currentPlayer = 1;
private int piecesPlayer1 = 12;
private int piecesPlayer2 = 12;
private int turnsPlayed = 0;
@Override
protected void initializeGame() {
System.out.println("Checkers Game Initialized!");
System.out.println("Setting up checkers board...");
System.out.println("Each player has 12 pieces");
currentPlayer = 1;
piecesPlayer1 = 12;
piecesPlayer2 = 12;
turnsPlayed = 0;
}
@Override
protected void makePlay() {
turnsPlayed++;
System.out.println("Player " + currentPlayer + " makes move...");
// Simulate capturing a piece
if (turnsPlayed % 3 == 0) {
if (currentPlayer == 1) {
piecesPlayer2--;
System.out.println("Player 1 captured a piece!");
} else {
piecesPlayer1--;
System.out.println("Player 2 captured a piece!");
}
}
// Switch players
currentPlayer = (currentPlayer == 1) ? 2 : 1;
}
@Override
protected boolean isGameOver() {
if (piecesPlayer1 == 0) {
System.out.println("All Player 1 pieces captured!");
return true;
}
if (piecesPlayer2 == 0) {
System.out.println("All Player 2 pieces captured!");
return true;
}
if (turnsPlayed >= 20) { // Limit for demo
System.out.println("Move limit reached!");
return true;
}
return false;
}
@Override
protected void displayStatus() {
System.out.println("Checkers Status:");
System.out.println(" Player 1 pieces: " + piecesPlayer1);
System.out.println(" Player 2 pieces: " + piecesPlayer2);
System.out.println(" Turns played: " + turnsPlayed);
}
@Override
protected void displayWinner() {
if (piecesPlayer1 > piecesPlayer2) {
System.out.println("Player 1 wins with " + piecesPlayer1 + " pieces remaining!");
} else if (piecesPlayer2 > piecesPlayer1) {
System.out.println("Player 2 wins with " + piecesPlayer2 + " pieces remaining!");
} else {
System.out.println("It's a tie!");
}
}
}
GameDemo.java
java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Client code
public class GameDemo {
public static void main(String[] args) {
System.out.println("=== PLAYING CHESS ===\n");
Game chess = new Chess();
chess.play();
System.out.println("\n" + "=".repeat(50) + "\n");
System.out.println("=== PLAYING CHECKERS ===\n");
Game checkers = new Checkers();
checkers.play();
}
}

🌍 Real-World Examples

  • 🍳Recipe Following: Same steps (prep, cook, serve) but different ingredients
  • 🏗️Build Processes: compile → test → package → deploy (same steps, different tools)
  • 📊Data Import: read → validate → transform → save (same flow, different formats)
  • 🌐Web Frameworks: request handling flow with customizable hooks
  • 🧪Testing Frameworks: setUp → test → tearDown pattern

✅ Benefits

  • Code Reuse: Common code is written once in the base class
  • Control: The algorithm structure is controlled by the parent class
  • Flexibility: Subclasses can customize specific steps without affecting others
  • Consistency: Ensures all implementations follow the same algorithm flow
  • Maintenance: Changes to common steps only need to be made in one place

⚠️ Drawbacks

  • ⚠️Inflexibility: The algorithm structure is fixed and cannot be changed by subclasses
  • ⚠️Liskov Violation: Some implementations might become awkward if not all steps apply
  • ⚠️Complexity: Increases number of classes and inheritance depth
  • ⚠️Maintenance: Changes to the template method affect all subclasses

🔑 Key Points to Remember

  • 1️⃣Template Method defines algorithm SKELETON, subclasses fill in the DETAILS
  • 2️⃣The template method should be FINAL to prevent subclasses from changing the flow
  • 3️⃣Use ABSTRACT methods for required steps, HOOKS for optional customization
  • 4️⃣Hollywood Principle: "Don't call us, we'll call you" - parent calls child methods
  • 5️⃣Common in frameworks where flow is fixed but behavior varies
  • 6️⃣Different from Strategy - Strategy changes entire algorithm, Template Method changes steps

💪 Practice Scenarios

  • Create a document generator template for PDF, Word, and HTML with same structure
  • Build a data migration template for different database types
  • Implement an order processing template with different payment and shipping methods
  • Design a report generation template with different formats and data sources
  • Create an authentication template supporting different auth providers