Template Method Pattern
Template Method Pattern
Section titled “Template Method Pattern”The Template Method Pattern is one of the most fundamental behavioral design patterns that defines the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the algorithm’s structure. Understanding the Template Method Pattern is essential for building flexible, maintainable software systems.
Why Template Method Pattern?
Section titled “Why Template Method Pattern?”Imagine you’re making different types of beverages - tea and coffee. The process is similar: boil water, brew, pour, add condiments. But “brew” means steeping tea leaves for tea, and filtering coffee for coffee. The Template Method Pattern lets you define this common process once, with specific steps filled in by subclasses!
The Template Method Pattern defines the skeleton of an algorithm in a base class method, deferring some steps to subclasses. It lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.
What’s the Use of Template Method Pattern?
Section titled “What’s the Use of Template Method Pattern?”The Template Method Pattern is useful when:
- You have a common algorithm structure - Same steps, different details
- You want to avoid code duplication - Common code in base class
- You need to control extension points - Subclasses can only customize certain steps
- You want to enforce algorithm structure - Subclasses can’t change the order
- You have invariant steps - Some steps must always happen
What Happens If We Don’t Use Template Method Pattern?
Section titled “What Happens If We Don’t Use Template Method Pattern?”Without the Template Method Pattern, you might:
- Duplicate code - Same algorithm structure in multiple places
- Inconsistent implementations - Different classes implement differently
- Hard to maintain - Changes needed in multiple places
- No control over extensions - Subclasses can change everything
- Violate DRY - Don’t Repeat Yourself principle violated
Simple Example: The Beverage Maker
Section titled “Simple Example: The Beverage Maker”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Algorithm Flow
Section titled “Algorithm Flow”Here’s how the template method works - same structure, different implementations:
sequenceDiagram
participant Client
participant Tea
participant Coffee
participant BaseClass as Beverage (Base)
Client->>Tea: prepare_recipe()
activate Tea
Tea->>BaseClass: boil_water()
BaseClass-->>Tea: Water boiled
Tea->>Tea: brew() [steep tea]
Note right of Tea: Subclass implementation
Tea->>BaseClass: pour_in_cup()
BaseClass-->>Tea: Poured
Tea->>Tea: add_condiments() [add lemon]
Note right of Tea: Subclass implementation
Tea-->>Client: Tea ready!
deactivate Tea
Client->>Coffee: prepare_recipe()
activate Coffee
Coffee->>BaseClass: boil_water()
BaseClass-->>Coffee: Water boiled
Coffee->>Coffee: brew() [drip coffee]
Note right of Coffee: Different implementation
Coffee->>BaseClass: pour_in_cup()
BaseClass-->>Coffee: Poured
Coffee->>Coffee: add_condiments() [add milk]
Note right of Coffee: Different implementation
Coffee-->>Client: Coffee ready!
deactivate Coffee
The Problem
Section titled “The Problem”You’re building a beverage maker that can make tea and coffee. Without Template Method Pattern:
# ❌ Without Template Method Pattern - Code duplication!
class Tea: def prepare(self): # Step 1: Boil water print("Boiling water...")
# Step 2: Brew (specific to tea) print("Steeping the tea...")
# Step 3: Pour print("Pouring into cup...")
# Step 4: Add condiments (specific to tea) print("Adding lemon...")
class Coffee: def prepare(self): # Step 1: Boil water - DUPLICATED! print("Boiling water...")
# Step 2: Brew (specific to coffee) print("Dripping coffee through filter...")
# Step 3: Pour - DUPLICATED! print("Pouring into cup...")
# Step 4: Add condiments (specific to coffee) print("Adding milk and sugar...")
class HotChocolate: def prepare(self): # Step 1: Boil water - DUPLICATED AGAIN! print("Boiling water...")
# Step 2: Mix (specific to hot chocolate) print("Mixing chocolate powder...")
# Step 3: Pour - DUPLICATED AGAIN! print("Pouring into cup...")
# Step 4: Add condiments print("Adding marshmallows...")
# Problems:# - boil_water() and pour() duplicated in EVERY class# - If we change how we boil water, change in EVERY class# - No enforcement of algorithm structure# - Easy to forget a step// ❌ Without Template Method Pattern - Code duplication!
class Tea { public void prepare() { // Step 1: Boil water System.out.println("Boiling water...");
// Step 2: Brew (specific to tea) System.out.println("Steeping the tea...");
// Step 3: Pour System.out.println("Pouring into cup...");
// Step 4: Add condiments (specific to tea) System.out.println("Adding lemon..."); }}
class Coffee { public void prepare() { // Step 1: Boil water - DUPLICATED! System.out.println("Boiling water...");
// Step 2: Brew (specific to coffee) System.out.println("Dripping coffee through filter...");
// Step 3: Pour - DUPLICATED! System.out.println("Pouring into cup...");
// Step 4: Add condiments (specific to coffee) System.out.println("Adding milk and sugar..."); }}
class HotChocolate { public void prepare() { // Step 1: Boil water - DUPLICATED AGAIN! System.out.println("Boiling water...");
// Step 2: Mix (specific to hot chocolate) System.out.println("Mixing chocolate powder...");
// Step 3: Pour - DUPLICATED AGAIN! System.out.println("Pouring into cup...");
// Step 4: Add condiments System.out.println("Adding marshmallows..."); }}
// Problems:// - boilWater() and pour() duplicated in EVERY class// - If we change how we boil water, change in EVERY class// - No enforcement of algorithm structure// - Easy to forget a stepProblems:
- Code duplication - Same steps copied everywhere
- Maintenance nightmare - Change in multiple places
- No structure enforcement - Easy to forget steps
- Violates DRY principle
The Solution: Template Method Pattern
Section titled “The Solution: Template Method Pattern”Class Structure
Section titled “Class Structure”classDiagram
class Beverage {
<<abstract>>
+prepare_recipe() void
+boil_water() void
#brew()* void
+pour_in_cup() void
#add_condiments()* void
}
class Tea {
#brew() void
#add_condiments() void
}
class Coffee {
#brew() void
#add_condiments() void
}
class HotChocolate {
#brew() void
#add_condiments() void
}
Beverage <|-- Tea : extends
Beverage <|-- Coffee : extends
Beverage <|-- HotChocolate : extends
note for Beverage "Template method: prepare_recipe()\nConcrete: boil_water(), pour_in_cup()\nAbstract: brew(), add_condiments()"
from abc import ABC, abstractmethod
# Step 1: Create the Abstract Class with Template Methodclass Beverage(ABC): """Abstract base class with template method"""
def prepare_recipe(self) -> None: """ Template method - defines the algorithm skeleton. This method is final - subclasses cannot override it! """ self.boil_water() self.brew() # Abstract - subclasses implement self.pour_in_cup() self.add_condiments() # Abstract - subclasses implement print(f"✅ {self.__class__.__name__} is ready!\n")
def boil_water(self) -> None: """Concrete method - same for all beverages""" print("🔥 Boiling water...")
def pour_in_cup(self) -> None: """Concrete method - same for all beverages""" print("☕ Pouring into cup...")
@abstractmethod def brew(self) -> None: """Abstract method - subclasses must implement""" pass
@abstractmethod def add_condiments(self) -> None: """Abstract method - subclasses must implement""" pass
# Step 2: Create Concrete Subclassesclass Tea(Beverage): """Concrete class - implements abstract methods for tea"""
def brew(self) -> None: print("🍵 Steeping the tea for 3 minutes...")
def add_condiments(self) -> None: print("🍋 Adding lemon slice...")
class Coffee(Beverage): """Concrete class - implements abstract methods for coffee"""
def brew(self) -> None: print("☕ Dripping coffee through filter...")
def add_condiments(self) -> None: print("🥛 Adding milk and sugar...")
class HotChocolate(Beverage): """Concrete class - implements abstract methods for hot chocolate"""
def brew(self) -> None: print("🍫 Mixing chocolate powder with water...")
def add_condiments(self) -> None: print("🍡 Adding marshmallows and whipped cream...")
# Step 3: Use the patterndef main(): print("=" * 50) print("Beverage Maker (Template Method Pattern)") print("=" * 50)
# Make tea print("\n📋 Making Tea:") print("-" * 30) tea = Tea() tea.prepare_recipe()
# Make coffee print("📋 Making Coffee:") print("-" * 30) coffee = Coffee() coffee.prepare_recipe()
# Make hot chocolate print("📋 Making Hot Chocolate:") print("-" * 30) hot_chocolate = HotChocolate() hot_chocolate.prepare_recipe()
print("=" * 50) print("✅ Template Method Pattern: Same algorithm, different details!") print("✅ Common code in base class - no duplication!")
if __name__ == "__main__": main()// Step 1: Create the Abstract Class with Template Methodabstract class Beverage { /** * Abstract base class with template method */
// Template method - defines the algorithm skeleton // This method is final - subclasses cannot override it! public final void prepareRecipe() { boilWater(); brew(); // Abstract - subclasses implement pourInCup(); addCondiments(); // Abstract - subclasses implement System.out.println("✅ " + getClass().getSimpleName() + " is ready!\n"); }
// Concrete method - same for all beverages private void boilWater() { System.out.println("🔥 Boiling water..."); }
// Concrete method - same for all beverages private void pourInCup() { System.out.println("☕ Pouring into cup..."); }
// Abstract method - subclasses must implement protected abstract void brew();
// Abstract method - subclasses must implement protected abstract void addCondiments();}
// Step 2: Create Concrete Subclassesclass Tea extends Beverage { /** * Concrete class - implements abstract methods for tea */ @Override protected void brew() { System.out.println("🍵 Steeping the tea for 3 minutes..."); }
@Override protected void addCondiments() { System.out.println("🍋 Adding lemon slice..."); }}
class Coffee extends Beverage { /** * Concrete class - implements abstract methods for coffee */ @Override protected void brew() { System.out.println("☕ Dripping coffee through filter..."); }
@Override protected void addCondiments() { System.out.println("🥛 Adding milk and sugar..."); }}
class HotChocolate extends Beverage { /** * Concrete class - implements abstract methods for hot chocolate */ @Override protected void brew() { System.out.println("🍫 Mixing chocolate powder with water..."); }
@Override protected void addCondiments() { System.out.println("🍡 Adding marshmallows and whipped cream..."); }}
// Step 3: Use the patternpublic class Main { public static void main(String[] args) { System.out.println("=".repeat(50)); System.out.println("Beverage Maker (Template Method Pattern)"); System.out.println("=".repeat(50));
// Make tea System.out.println("\n📋 Making Tea:"); System.out.println("-".repeat(30)); Beverage tea = new Tea(); tea.prepareRecipe();
// Make coffee System.out.println("📋 Making Coffee:"); System.out.println("-".repeat(30)); Beverage coffee = new Coffee(); coffee.prepareRecipe();
// Make hot chocolate System.out.println("📋 Making Hot Chocolate:"); System.out.println("-".repeat(30)); Beverage hotChocolate = new HotChocolate(); hotChocolate.prepareRecipe();
System.out.println("=".repeat(50)); System.out.println("✅ Template Method Pattern: Same algorithm, different details!"); System.out.println("✅ Common code in base class - no duplication!"); }}Real-World Software Example: Data Mining Pipeline
Section titled “Real-World Software Example: Data Mining Pipeline”Now let’s see a realistic software example - a data mining pipeline that processes data from different sources.
The Problem
Section titled “The Problem”You’re building a data processing system that needs to handle CSV files, JSON APIs, and databases. The process is similar: open connection, extract data, transform, analyze, generate report, close connection. Without Template Method:
# ❌ Without Template Method Pattern - Duplicated process!
class CSVDataMiner: def mine(self, path: str): # Open - specific to CSV print(f"Opening CSV file: {path}")
# Extract - specific to CSV print("Parsing CSV rows...") data = [{"name": "Alice"}, {"name": "Bob"}]
# Transform - COMMON! print("Transforming data...") transformed = [d["name"].upper() for d in data]
# Analyze - COMMON! print("Analyzing data...") result = {"count": len(transformed)}
# Generate report - COMMON! print(f"Report: {result}")
# Close - specific to CSV print("Closing CSV file")
class JSONAPIMiner: def mine(self, url: str): # Open - specific to API print(f"Connecting to API: {url}")
# Extract - specific to API print("Fetching JSON from API...") data = [{"name": "Charlie"}, {"name": "Diana"}]
# Transform - DUPLICATED! print("Transforming data...") transformed = [d["name"].upper() for d in data]
# Analyze - DUPLICATED! print("Analyzing data...") result = {"count": len(transformed)}
# Generate report - DUPLICATED! print(f"Report: {result}")
# Close - specific to API print("Closing API connection")
# Problems:# - transform(), analyze(), generate_report() duplicated# - If analysis logic changes, update ALL miners# - No guarantee all miners follow same process// ❌ Without Template Method Pattern - Duplicated process!
class CSVDataMiner { public void mine(String path) { // Open - specific to CSV System.out.println("Opening CSV file: " + path);
// Extract - specific to CSV System.out.println("Parsing CSV rows..."); List<Map<String, String>> data = List.of( Map.of("name", "Alice"), Map.of("name", "Bob") );
// Transform - COMMON! System.out.println("Transforming data...");
// Analyze - COMMON! System.out.println("Analyzing data...");
// Generate report - COMMON! System.out.println("Report generated");
// Close - specific to CSV System.out.println("Closing CSV file"); }}
class JSONAPIMiner { public void mine(String url) { // Open - specific to API System.out.println("Connecting to API: " + url);
// Extract - specific to API System.out.println("Fetching JSON from API...");
// Transform - DUPLICATED! System.out.println("Transforming data...");
// Analyze - DUPLICATED! System.out.println("Analyzing data...");
// Generate report - DUPLICATED! System.out.println("Report generated");
// Close - specific to API System.out.println("Closing API connection"); }}
// Problems:// - transform(), analyze(), generateReport() duplicated// - If analysis logic changes, update ALL miners// - No guarantee all miners follow same processProblems:
- Common steps duplicated across all miners
- Changes need to be made in multiple places
- No enforcement of the mining process
The Solution: Template Method Pattern
Section titled “The Solution: Template Method Pattern”Class Structure
Section titled “Class Structure”classDiagram
class DataMiner {
<<abstract>>
+mine(source) void
#open_source(source)* void
#extract_data()* List
+transform_data(data) List
+analyze_data(data) Report
+generate_report(analysis) void
#close_source()* void
+hook_before_analysis() void
}
class CSVDataMiner {
#open_source(source) void
#extract_data() List
#close_source() void
}
class JSONAPIMiner {
#open_source(source) void
#extract_data() List
#close_source() void
}
class DatabaseMiner {
#open_source(source) void
#extract_data() List
#close_source() void
+hook_before_analysis() void
}
DataMiner <|-- CSVDataMiner : extends
DataMiner <|-- JSONAPIMiner : extends
DataMiner <|-- DatabaseMiner : extends
note for DataMiner "Template method: mine()\nAbstract: open, extract, close\nConcrete: transform, analyze, report\nHook: hook_before_analysis()"
from abc import ABC, abstractmethodfrom typing import List, Dict, Anyfrom dataclasses import dataclass
# Step 1: Define data structures@dataclassclass AnalysisReport: """Report generated from data analysis""" source_type: str record_count: int summary: Dict[str, Any]
# Step 2: Create the Abstract Class with Template Methodclass DataMiner(ABC): """Abstract data miner with template method"""
def mine(self, source: str) -> AnalysisReport: """ Template method - defines the data mining algorithm. Subclasses cannot change this structure! """ print(f"\n{'='*50}") print(f"📊 Starting data mining: {self.__class__.__name__}") print(f"{'='*50}")
# Step 1: Open source (abstract - source-specific) self.open_source(source)
# Step 2: Extract data (abstract - source-specific) raw_data = self.extract_data() print(f" 📥 Extracted {len(raw_data)} records")
# Step 3: Transform data (concrete - common for all) transformed_data = self.transform_data(raw_data)
# Hook: Optional step before analysis self.hook_before_analysis(transformed_data)
# Step 4: Analyze data (concrete - common for all) report = self.analyze_data(transformed_data)
# Step 5: Generate report (concrete - common for all) self.generate_report(report)
# Step 6: Close source (abstract - source-specific) self.close_source()
return report
# Abstract methods - MUST be implemented by subclasses @abstractmethod def open_source(self, source: str) -> None: """Open the data source""" pass
@abstractmethod def extract_data(self) -> List[Dict[str, Any]]: """Extract raw data from source""" pass
@abstractmethod def close_source(self) -> None: """Close the data source""" pass
# Concrete methods - Common implementation for all def transform_data(self, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """Transform raw data - common for all miners""" print(" 🔄 Transforming data...") transformed = [] for record in data: # Normalize keys to lowercase transformed.append({k.lower(): v for k, v in record.items()}) return transformed
def analyze_data(self, data: List[Dict[str, Any]]) -> AnalysisReport: """Analyze data - common for all miners""" print(" 📈 Analyzing data...")
# Calculate statistics summary = { "fields": list(data[0].keys()) if data else [], "sample": data[:2] if len(data) >= 2 else data }
return AnalysisReport( source_type=self.__class__.__name__, record_count=len(data), summary=summary )
def generate_report(self, report: AnalysisReport) -> None: """Generate report - common for all miners""" print(" 📝 Generating report...") print(f" Source: {report.source_type}") print(f" Records: {report.record_count}") print(f" Fields: {report.summary['fields']}")
# Hook method - CAN be overridden, but not required def hook_before_analysis(self, data: List[Dict[str, Any]]) -> None: """Hook - optional step before analysis. Override if needed.""" pass
# Step 3: Create Concrete Subclassesclass CSVDataMiner(DataMiner): """Mines data from CSV files"""
def __init__(self): self._file = None
def open_source(self, source: str) -> None: print(f" 📂 Opening CSV file: {source}") self._file = source # Simulated file handle
def extract_data(self) -> List[Dict[str, Any]]: print(" 📋 Parsing CSV rows...") # Simulated CSV data return [ {"Name": "Alice", "Age": "30", "City": "NYC"}, {"Name": "Bob", "Age": "25", "City": "LA"}, {"Name": "Charlie", "Age": "35", "City": "Chicago"}, ]
def close_source(self) -> None: print(f" 📁 Closing CSV file") self._file = None
class JSONAPIMiner(DataMiner): """Mines data from JSON APIs"""
def __init__(self): self._connection = None
def open_source(self, source: str) -> None: print(f" 🌐 Connecting to API: {source}") self._connection = source # Simulated connection
def extract_data(self) -> List[Dict[str, Any]]: print(" 📡 Fetching JSON from API...") # Simulated API response return [ {"id": 1, "username": "alice_dev", "active": True}, {"id": 2, "username": "bob_admin", "active": True}, {"id": 3, "username": "charlie_user", "active": False}, ]
def close_source(self) -> None: print(f" 🔌 Closing API connection") self._connection = None
class DatabaseMiner(DataMiner): """Mines data from databases"""
def __init__(self): self._connection = None
def open_source(self, source: str) -> None: print(f" 🗄️ Connecting to database: {source}") self._connection = source # Simulated DB connection
def extract_data(self) -> List[Dict[str, Any]]: print(" 🔍 Executing SQL query...") # Simulated database result return [ {"order_id": 1001, "amount": 99.99, "status": "completed"}, {"order_id": 1002, "amount": 149.99, "status": "pending"}, {"order_id": 1003, "amount": 29.99, "status": "completed"}, {"order_id": 1004, "amount": 199.99, "status": "cancelled"}, ]
def close_source(self) -> None: print(f" 🔒 Closing database connection") self._connection = None
# Override hook to add custom behavior def hook_before_analysis(self, data: List[Dict[str, Any]]) -> None: """Custom hook - validate data before analysis""" print(" ✅ Validating database records...") invalid = [d for d in data if d.get("status") == "cancelled"] if invalid: print(f" ⚠️ Found {len(invalid)} cancelled orders")
# Step 4: Use the patterndef main(): print("=" * 60) print("Data Mining Pipeline (Template Method Pattern)") print("=" * 60)
# Mine CSV data csv_miner = CSVDataMiner() csv_miner.mine("users.csv")
# Mine JSON API data api_miner = JSONAPIMiner() api_miner.mine("https://api.example.com/users")
# Mine Database data (with custom hook) db_miner = DatabaseMiner() db_miner.mine("postgresql://localhost/orders")
print("\n" + "=" * 60) print("✅ Template Method Pattern: Same process, different sources!") print("✅ Common logic in base class - no duplication!") print("✅ Hooks allow optional customization!")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define data structuresclass AnalysisReport { /** * Report generated from data analysis */ private String sourceType; private int recordCount; private Map<String, Object> summary;
public AnalysisReport(String sourceType, int recordCount, Map<String, Object> summary) { this.sourceType = sourceType; this.recordCount = recordCount; this.summary = summary; }
public String getSourceType() { return sourceType; } public int getRecordCount() { return recordCount; } public Map<String, Object> getSummary() { return summary; }}
// Step 2: Create the Abstract Class with Template Methodabstract class DataMiner { /** * Abstract data miner with template method */
// Template method - defines the data mining algorithm // Subclasses cannot change this structure! public final AnalysisReport mine(String source) { System.out.println("\n" + "=".repeat(50)); System.out.println("📊 Starting data mining: " + getClass().getSimpleName()); System.out.println("=".repeat(50));
// Step 1: Open source (abstract - source-specific) openSource(source);
// Step 2: Extract data (abstract - source-specific) List<Map<String, Object>> rawData = extractData(); System.out.println(" 📥 Extracted " + rawData.size() + " records");
// Step 3: Transform data (concrete - common for all) List<Map<String, Object>> transformedData = transformData(rawData);
// Hook: Optional step before analysis hookBeforeAnalysis(transformedData);
// Step 4: Analyze data (concrete - common for all) AnalysisReport report = analyzeData(transformedData);
// Step 5: Generate report (concrete - common for all) generateReport(report);
// Step 6: Close source (abstract - source-specific) closeSource();
return report; }
// Abstract methods - MUST be implemented by subclasses protected abstract void openSource(String source); protected abstract List<Map<String, Object>> extractData(); protected abstract void closeSource();
// Concrete methods - Common implementation for all protected List<Map<String, Object>> transformData(List<Map<String, Object>> data) { System.out.println(" 🔄 Transforming data..."); List<Map<String, Object>> transformed = new ArrayList<>(); for (Map<String, Object> record : data) { Map<String, Object> newRecord = new HashMap<>(); for (Map.Entry<String, Object> entry : record.entrySet()) { newRecord.put(entry.getKey().toLowerCase(), entry.getValue()); } transformed.add(newRecord); } return transformed; }
protected AnalysisReport analyzeData(List<Map<String, Object>> data) { System.out.println(" 📈 Analyzing data...");
Map<String, Object> summary = new HashMap<>(); summary.put("fields", data.isEmpty() ? List.of() : new ArrayList<>(data.get(0).keySet())); summary.put("sample", data.size() >= 2 ? data.subList(0, 2) : data);
return new AnalysisReport( getClass().getSimpleName(), data.size(), summary ); }
protected void generateReport(AnalysisReport report) { System.out.println(" 📝 Generating report..."); System.out.println(" Source: " + report.getSourceType()); System.out.println(" Records: " + report.getRecordCount()); System.out.println(" Fields: " + report.getSummary().get("fields")); }
// Hook method - CAN be overridden, but not required protected void hookBeforeAnalysis(List<Map<String, Object>> data) { // Default: do nothing. Override if needed. }}
// Step 3: Create Concrete Subclassesclass CSVDataMiner extends DataMiner { /** * Mines data from CSV files */ private String file;
@Override protected void openSource(String source) { System.out.println(" 📂 Opening CSV file: " + source); this.file = source; }
@Override protected List<Map<String, Object>> extractData() { System.out.println(" 📋 Parsing CSV rows..."); return Arrays.asList( Map.of("Name", "Alice", "Age", "30", "City", "NYC"), Map.of("Name", "Bob", "Age", "25", "City", "LA"), Map.of("Name", "Charlie", "Age", "35", "City", "Chicago") ); }
@Override protected void closeSource() { System.out.println(" 📁 Closing CSV file"); this.file = null; }}
class JSONAPIMiner extends DataMiner { /** * Mines data from JSON APIs */ private String connection;
@Override protected void openSource(String source) { System.out.println(" 🌐 Connecting to API: " + source); this.connection = source; }
@Override protected List<Map<String, Object>> extractData() { System.out.println(" 📡 Fetching JSON from API..."); return Arrays.asList( Map.of("id", 1, "username", "alice_dev", "active", true), Map.of("id", 2, "username", "bob_admin", "active", true), Map.of("id", 3, "username", "charlie_user", "active", false) ); }
@Override protected void closeSource() { System.out.println(" 🔌 Closing API connection"); this.connection = null; }}
class DatabaseMiner extends DataMiner { /** * Mines data from databases */ private String connection;
@Override protected void openSource(String source) { System.out.println(" 🗄️ Connecting to database: " + source); this.connection = source; }
@Override protected List<Map<String, Object>> extractData() { System.out.println(" 🔍 Executing SQL query..."); return Arrays.asList( Map.of("order_id", 1001, "amount", 99.99, "status", "completed"), Map.of("order_id", 1002, "amount", 149.99, "status", "pending"), Map.of("order_id", 1003, "amount", 29.99, "status", "completed"), Map.of("order_id", 1004, "amount", 199.99, "status", "cancelled") ); }
@Override protected void closeSource() { System.out.println(" 🔒 Closing database connection"); this.connection = null; }
// Override hook to add custom behavior @Override protected void hookBeforeAnalysis(List<Map<String, Object>> data) { System.out.println(" ✅ Validating database records..."); long invalidCount = data.stream() .filter(d -> "cancelled".equals(d.get("status"))) .count(); if (invalidCount > 0) { System.out.println(" ⚠️ Found " + invalidCount + " cancelled orders"); } }}
// Step 4: Use the patternpublic class Main { public static void main(String[] args) { System.out.println("=".repeat(60)); System.out.println("Data Mining Pipeline (Template Method Pattern)"); System.out.println("=".repeat(60));
// Mine CSV data DataMiner csvMiner = new CSVDataMiner(); csvMiner.mine("users.csv");
// Mine JSON API data DataMiner apiMiner = new JSONAPIMiner(); apiMiner.mine("https://api.example.com/users");
// Mine Database data (with custom hook) DataMiner dbMiner = new DatabaseMiner(); dbMiner.mine("postgresql://localhost/orders");
System.out.println("\n" + "=".repeat(60)); System.out.println("✅ Template Method Pattern: Same process, different sources!"); System.out.println("✅ Common logic in base class - no duplication!"); System.out.println("✅ Hooks allow optional customization!"); }}Template Method Pattern Variants
Section titled “Template Method Pattern Variants”1. With Hooks
Section titled “1. With Hooks”Hooks are optional methods that can be overridden:
# Template Method with Hooksclass Beverage(ABC): def prepare_recipe(self): self.boil_water() self.brew() self.pour_in_cup()
# Hook - optional step if self.customer_wants_condiments(): self.add_condiments()
# Hook with default behavior def customer_wants_condiments(self) -> bool: """Hook - subclasses can override to change behavior""" return True # Default: yes
class TeaWithHook(Beverage): def customer_wants_condiments(self) -> bool: # Ask the customer answer = input("Would you like lemon? (y/n): ") return answer.lower() == 'y'// Template Method with Hooksabstract class Beverage { public final void prepareRecipe() { boilWater(); brew(); pourInCup();
// Hook - optional step if (customerWantsCondiments()) { addCondiments(); } }
// Hook with default behavior protected boolean customerWantsCondiments() { // Default: yes return true; }
protected abstract void brew(); protected abstract void addCondiments();}
class TeaWithHook extends Beverage { @Override protected boolean customerWantsCondiments() { // Could ask the customer return true; }}Hooks allow:
- Optional customization without forcing implementation
- Conditional execution of steps
- Default behavior that can be changed
2. Hollywood Principle
Section titled “2. Hollywood Principle”“Don’t call us, we’ll call you” - Base class calls subclass methods:
sequenceDiagram
participant Client
participant BaseClass
participant SubClass
Client->>BaseClass: templateMethod()
BaseClass->>BaseClass: step1()
BaseClass->>SubClass: abstractStep2()
Note right of SubClass: Subclass method called by base
SubClass-->>BaseClass: return
BaseClass->>BaseClass: step3()
BaseClass-->>Client: done
When to Use Template Method Pattern?
Section titled “When to Use Template Method Pattern?”Use Template Method Pattern when:
✅ Common algorithm structure - Same steps, different implementations
✅ Code duplication - Same structure repeated in multiple classes
✅ Control extension points - Only certain steps can be customized
✅ Enforce invariant steps - Some steps must always happen
✅ Hollywood Principle - Framework calls user code
When NOT to Use Template Method Pattern?
Section titled “When NOT to Use Template Method Pattern?”Don’t use Template Method Pattern when:
❌ Algorithms are completely different - No common structure
❌ All steps need customization - Use Strategy instead
❌ Inheritance isn’t appropriate - Use composition
❌ Simple cases - Just a few steps, no duplication
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Making Template Method Overridable
Section titled “Mistake 1: Making Template Method Overridable”# ❌ Bad: Template method can be overriddenclass BadBase: def template_method(self): # Not protected! self.step1() self.step2() self.step3() # Subclasses can completely change the algorithm!
# ✅ Good: Template method is final (in concept)class GoodBase(ABC): def template_method(self): """ Template method - DO NOT OVERRIDE! Note: Python doesn't have 'final', but document it clearly. """ self.step1() self.step2() self.step3()// ❌ Bad: Template method can be overriddenclass BadBase { public void templateMethod() { // Not final! step1(); step2(); step3(); } // Subclasses can completely change the algorithm!}
// ✅ Good: Template method is finalabstract class GoodBase { public final void templateMethod() { // final! step1(); step2(); step3(); } // Subclasses cannot override the algorithm structure}Mistake 2: Too Many Abstract Methods
Section titled “Mistake 2: Too Many Abstract Methods”# ❌ Bad: Everything is abstract - defeats the purpose!class BadBase(ABC): def template_method(self): self.step1() self.step2() self.step3() self.step4() self.step5()
@abstractmethod def step1(self): pass @abstractmethod def step2(self): pass @abstractmethod def step3(self): pass @abstractmethod def step4(self): pass @abstractmethod def step5(self): pass # All steps abstract = no code reuse!
# ✅ Good: Mix of concrete and abstract methodsclass GoodBase(ABC): def template_method(self): self.setup() # Concrete self.do_work() # Abstract self.cleanup() # Concrete
def setup(self): # Common implementation print("Setting up...")
@abstractmethod def do_work(self): pass # Customization point
def cleanup(self): # Common implementation print("Cleaning up...")// ❌ Bad: Everything is abstract - defeats the purpose!abstract class BadBase { public final void templateMethod() { step1(); step2(); step3(); step4(); step5(); }
protected abstract void step1(); protected abstract void step2(); protected abstract void step3(); protected abstract void step4(); protected abstract void step5(); // All steps abstract = no code reuse!}
// ✅ Good: Mix of concrete and abstract methodsabstract class GoodBase { public final void templateMethod() { setup(); // Concrete doWork(); // Abstract cleanup(); // Concrete }
protected void setup() { // Common implementation System.out.println("Setting up..."); }
protected abstract void doWork(); // Customization point
protected void cleanup() { // Common implementation System.out.println("Cleaning up..."); }}Mistake 3: Not Using Hooks for Optional Steps
Section titled “Mistake 3: Not Using Hooks for Optional Steps”# ❌ Bad: Forcing subclasses to implement optional stepsclass BadBase(ABC): def template_method(self): self.required_step() self.optional_step() # Abstract - but not all need it!
@abstractmethod def optional_step(self): pass # Forces empty implementations
# ✅ Good: Use hooks for optional stepsclass GoodBase(ABC): def template_method(self): self.required_step() self.optional_hook() # Hook with default
@abstractmethod def required_step(self): pass
def optional_hook(self): """Hook - override only if needed""" pass # Default: do nothing// ❌ Bad: Forcing subclasses to implement optional stepsabstract class BadBase { public final void templateMethod() { requiredStep(); optionalStep(); // Abstract - but not all need it! }
protected abstract void optionalStep(); // Forces empty implementations}
// ✅ Good: Use hooks for optional stepsabstract class GoodBase { public final void templateMethod() { requiredStep(); optionalHook(); // Hook with default }
protected abstract void requiredStep();
protected void optionalHook() { // Default: do nothing. Override only if needed. }}Benefits of Template Method Pattern
Section titled “Benefits of Template Method Pattern”- Code Reuse - Common code in base class
- Algorithm Structure - Can’t be changed by subclasses
- Single Point of Change - Fix bugs once
- Controlled Extension - Only specific steps customizable
- Inversion of Control - Hollywood Principle
- DRY Principle - Don’t Repeat Yourself
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Template Method Pattern?
Section titled “What is Template Method Pattern?”Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class method, letting subclasses override specific steps without changing the algorithm’s structure.
Why Use It?
Section titled “Why Use It?”- ✅ Code reuse - Common steps in base class
- ✅ Algorithm structure - Fixed, can’t be changed
- ✅ Controlled extension - Only specific steps customizable
- ✅ DRY principle - No duplication
- ✅ Hollywood Principle - “Don’t call us, we’ll call you”
How It Works?
Section titled “How It Works?”- Create abstract base class - With template method
- Define template method - The algorithm skeleton
- Add concrete methods - Common implementations
- Add abstract methods - Customization points
- Add hooks - Optional customization
- Create subclasses - Implement abstract methods
Key Components
Section titled “Key Components”Abstract Class (Template Method)├── template_method() → defines algorithm├── concrete_step() → common implementation├── abstract_step() → subclass implements└── hook() → optional overrideSimple Example
Section titled “Simple Example”class Beverage(ABC): def prepare_recipe(self): # Template method self.boil_water() # Concrete self.brew() # Abstract self.pour_in_cup() # Concrete self.add_condiments() # Abstract
def boil_water(self): print("Boiling water")
@abstractmethod def brew(self): pass
@abstractmethod def add_condiments(self): passWhen to Use?
Section titled “When to Use?”✅ Common algorithm structure
✅ Code duplication in algorithm
✅ Need to control extension points
✅ Invariant steps must happen
✅ Framework development
When NOT to Use?
Section titled “When NOT to Use?”❌ Completely different algorithms
❌ All steps need customization
❌ Simple cases with no duplication
Key Takeaways
Section titled “Key Takeaways”- Template Method = Algorithm skeleton in base class
- Abstract methods = Customization points
- Concrete methods = Common implementation
- Hooks = Optional customization
- Hollywood Principle = Base calls subclass methods
Interview Focus: Template Method Pattern
Section titled “Interview Focus: Template Method Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in a base class method, letting subclasses override specific steps without changing the algorithm’s structure. It promotes code reuse and enforces a consistent algorithm structure.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of inheritance-based patterns
- Indicates you can explain concepts clearly
2. Template Method vs Strategy Pattern
Section titled “2. Template Method vs Strategy Pattern”Must discuss:
- Template Method - Uses inheritance, algorithm structure fixed
- Strategy - Uses composition, entire algorithm swapped
- Key difference - Template customizes steps, Strategy replaces algorithm
Example to give:
“Template Method is like a form with blanks to fill in - the structure is fixed, you just provide specific content. Strategy is like choosing a completely different form. Template Method changes parts of an algorithm; Strategy changes the entire algorithm.”
3. Hollywood Principle
Section titled “3. Hollywood Principle”Must explain:
- “Don’t call us, we’ll call you”
- Base class calls subclass methods (inversion of control)
- Framework pattern - framework controls flow
Example to give:
“In Template Method, the base class controls when subclass methods are called - this is the Hollywood Principle. The subclass doesn’t call the base class to get things done; instead, the base class calls down to the subclass when it needs specific implementation.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Code reuse - Common code in base class
- Consistent structure - Algorithm can’t be altered
- Single point of change - Fix bugs once
- Controlled extension - Limited customization points
- DRY principle - No duplication
Trade-offs to acknowledge:
- Inheritance limitation - Java single inheritance
- Harder to understand - Need to trace through hierarchy
- Tight coupling - Subclasses tied to base class structure
- Fragile base class - Changes to base affect all
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “When would you use Template Method over Strategy?”
A:
“I’d use Template Method when I have a fixed algorithm structure with specific steps that vary. For example, a data processing pipeline where opening, transforming, and closing are always the same, but extracting data varies by source. I’d use Strategy when I want to swap the entire algorithm, like different sorting algorithms that have completely different approaches.”
Q: “What are hooks in Template Method?”
A:
“Hooks are methods with default (usually empty) implementations that subclasses CAN override but don’t have to. Unlike abstract methods which MUST be overridden, hooks are optional. They provide additional customization points without forcing implementation. For example, a hook before saving that’s empty by default but can add validation.”
Q: “How does Template Method relate to SOLID principles?”
A:
“Template Method supports Open/Closed Principle - the algorithm structure is closed for modification but open for extension via abstract methods. It can violate Single Responsibility if the base class does too much. It demonstrates Dependency Inversion at the method level - the high-level algorithm depends on abstractions (abstract methods), not concrete implementations.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Template Method Pattern clearly
- Explain the difference from Strategy Pattern
- Describe the Hollywood Principle
- Implement Template Method from scratch
- List benefits and trade-offs
- Explain hooks vs abstract methods
- Give 2-3 real-world examples
- Discuss when NOT to use it
- Connect to SOLID principles
Remember: Template Method Pattern is about defining the skeleton of an algorithm - let subclasses fill in the details without changing the structure! 📋