Skip to main content

Template Method Pattern 📋

Definition: The Template Method pattern defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.

🎯 Intent

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

🤔 Problem

You have several classes that perform similar operations with the same sequence of steps, but the implementation details differ:

  • Code Duplication: Similar algorithms with minor variations
  • Algorithm Structure: Want to ensure consistent algorithm structure
  • Step Variations: Some steps need different implementations
  • Extension Points: Need to allow customization at specific points

Without the pattern, you'd have to duplicate the entire algorithm in each class.

💡 Solution

The Template Method pattern suggests breaking down the algorithm into a series of steps and defining the overall structure in a base class. Subclasses can override specific steps while keeping the algorithm structure intact.

🏗️ Structure

AbstractClass
├── +templateMethod(): void → step1() + step2() + step3()
├── +step1(): void (concrete)
├── #step2(): void (abstract - must implement)
├── #step3(): void (hook - can override)

ConcreteClass1, ConcreteClass2 extend AbstractClass
├── +step2(): void (required implementation)
├── +step3(): void (optional override)

💻 Simple Example

Data Processing Pipeline

// Abstract template class
class DataProcessor {
constructor() {
if (this.constructor === DataProcessor) {
throw new Error("DataProcessor is abstract and cannot be instantiated");
}
}

// Template method - defines the algorithm structure
processData(inputData) {
console.log("🔄 Starting data processing pipeline...\n");

// Step 1: Validate data (concrete method)
if (!this.validateData(inputData)) {
console.log("❌ Data validation failed");
return null;
}

// Step 2: Extract data (abstract method - must be implemented)
const extractedData = this.extractData(inputData);

// Step 3: Transform data (abstract method - must be implemented)
const transformedData = this.transformData(extractedData);

// Step 4: Filter data (hook method - can be overridden)
const filteredData = this.filterData(transformedData);

// Step 5: Save data (concrete method)
const result = this.saveData(filteredData);

// Step 6: Generate report (hook method - can be overridden)
this.generateReport(result);

console.log("✅ Data processing pipeline completed\n");
return result;
}

// Concrete method - same for all subclasses
validateData(inputData) {
console.log("🔍 Validating input data...");

if (!inputData || inputData.length === 0) {
console.log("❌ Input data is empty");
return false;
}

console.log(`✅ Data validation passed (${inputData.length} records)`);
return true;
}

// Abstract methods - must be implemented by subclasses
extractData(inputData) {
throw new Error("extractData() method must be implemented");
}

transformData(data) {
throw new Error("transformData() method must be implemented");
}

// Hook methods - optional to override
filterData(data) {
console.log("🔧 Applying default filtering (no filtering)");
return data;
}

generateReport(result) {
console.log(`📊 Generated default report: Processed ${result.length} records`);
}

// Concrete method - same for all subclasses
saveData(data) {
console.log(`💾 Saving ${data.length} processed records to database`);

// Simulate saving
const savedData = data.map((record, index) => ({
...record,
id: index + 1,
savedAt: new Date().toISOString()
}));

console.log("✅ Data saved successfully");
return savedData;
}
}

// Concrete implementation - CSV Data Processor
class CSVDataProcessor extends DataProcessor {
extractData(inputData) {
console.log("📄 Extracting data from CSV format...");

// Simulate CSV parsing
const extractedData = inputData.map(row => {
const [name, age, email, city] = row.split(',');
return { name: name?.trim(), age: age?.trim(), email: email?.trim(), city: city?.trim() };
});

console.log(`✅ Extracted ${extractedData.length} records from CSV`);
return extractedData;
}

transformData(data) {
console.log("🔄 Transforming CSV data...");

// Transform CSV data - normalize names, validate emails, etc.
const transformedData = data.map(record => ({
name: this.capitalizeName(record.name),
age: parseInt(record.age) || 0,
email: record.email?.toLowerCase(),
city: this.capitalizeCity(record.city),
type: 'csv_import'
})).filter(record => record.name && record.email);

console.log(`✅ Transformed ${transformedData.length} records`);
return transformedData;
}

filterData(data) {
console.log("🔧 Applying CSV-specific filtering (age > 0, valid email)");

const filteredData = data.filter(record =>
record.age > 0 && record.email && record.email.includes('@')
);

console.log(`✅ Filtered to ${filteredData.length} valid records`);
return filteredData;
}

generateReport(result) {
console.log("📊 Generating CSV processing report:");

const avgAge = result.reduce((sum, r) => sum + r.age, 0) / result.length;
const cities = [...new Set(result.map(r => r.city))];

console.log(` • Total records processed: ${result.length}`);
console.log(` • Average age: ${avgAge.toFixed(1)} years`);
console.log(` • Unique cities: ${cities.length} (${cities.slice(0, 3).join(', ')}${cities.length > 3 ? '...' : ''})`);
}

capitalizeName(name) {
return name ? name.split(' ').map(word =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
).join(' ') : '';
}

capitalizeCity(city) {
return city ? city.charAt(0).toUpperCase() + city.slice(1).toLowerCase() : '';
}
}

// Concrete implementation - JSON Data Processor
class JSONDataProcessor extends DataProcessor {
extractData(inputData) {
console.log("📄 Extracting data from JSON format...");

// Simulate JSON parsing
const extractedData = inputData.map(jsonStr => {
try {
return JSON.parse(jsonStr);
} catch (e) {
console.log(`⚠️ Invalid JSON: ${jsonStr}`);
return null;
}
}).filter(record => record !== null);

console.log(`✅ Extracted ${extractedData.length} records from JSON`);
return extractedData;
}

transformData(data) {
console.log("🔄 Transforming JSON data...");

// Transform JSON data - standardize field names, add metadata
const transformedData = data.map(record => ({
name: record.fullName || record.name || 'Unknown',
age: record.age || record.years || 0,
email: record.emailAddress || record.email || '',
city: record.location || record.city || 'Unknown',
type: 'json_import',
originalFields: Object.keys(record).length
}));

console.log(`✅ Transformed ${transformedData.length} records`);
return transformedData;
}

filterData(data) {
console.log("🔧 Applying JSON-specific filtering (has name and email)");

const filteredData = data.filter(record =>
record.name !== 'Unknown' && record.email !== '' && record.originalFields >= 3
);

console.log(`✅ Filtered to ${filteredData.length} complete records`);
return filteredData;
}

generateReport(result) {
console.log("📊 Generating JSON processing report:");

const fieldCounts = result.map(r => r.originalFields);
const avgFields = fieldCounts.reduce((sum, count) => sum + count, 0) / fieldCounts.length;
const emailDomains = [...new Set(result.map(r => r.email.split('@')[1]))];

console.log(` • Total records processed: ${result.length}`);
console.log(` • Average original fields: ${avgFields.toFixed(1)}`);
console.log(` • Email domains: ${emailDomains.length} (${emailDomains.slice(0, 3).join(', ')}${emailDomains.length > 3 ? '...' : ''})`);
}
}

// Concrete implementation - XML Data Processor
class XMLDataProcessor extends DataProcessor {
extractData(inputData) {
console.log("📄 Extracting data from XML format...");

// Simulate simple XML parsing
const extractedData = inputData.map(xmlStr => {
const nameMatch = xmlStr.match(/<name>(.*?)<\\/name>/);
const ageMatch = xmlStr.match(/<age>(.*?)<\\/age>/);
const emailMatch = xmlStr.match(/<email>(.*?)<\\/email>/);
const cityMatch = xmlStr.match(/<city>(.*?)<\\/city>/);

return {
name: nameMatch ? nameMatch[1] : '',
age: ageMatch ? ageMatch[1] : '0',
email: emailMatch ? emailMatch[1] : '',
city: cityMatch ? cityMatch[1] : ''
};
});

console.log(`✅ Extracted ${extractedData.length} records from XML`);
return extractedData;
}

transformData(data) {
console.log("🔄 Transforming XML data...");

const transformedData = data.map(record => ({
name: record.name.trim(),
age: parseInt(record.age) || 0,
email: record.email.trim().toLowerCase(),
city: record.city.trim(),
type: 'xml_import'
}));

console.log(`✅ Transformed ${transformedData.length} records`);
return transformedData;
}

// Using default filterData (no override)

generateReport(result) {
console.log("📊 Generating XML processing report:");
console.log(` • Total records processed: ${result.length}`);
console.log(` • Source format: XML`);
}
}

// Usage
console.log("=== Data Processing Template Method Demo ===\n");

console.log("Sample data preparation:");
console.log("-".repeat(25));

// Sample data for different formats
const csvData = [
"john doe,25,john.doe@email.com,new york",
"jane smith,30,jane.smith@email.com,los angeles",
"bob johnson,22,bob@email.com,chicago",
"invalid row",
"alice brown,28,alice.brown@email.com,seattle"
];

const jsonData = [
'{"fullName": "Mike Wilson", "age": 35, "emailAddress": "mike@email.com", "location": "Boston"}',
'{"name": "Sara Davis", "years": 29, "email": "sara@email.com", "city": "Miami"}',
'invalid json',
'{"fullName": "Tom Anderson", "age": 31, "emailAddress": "tom@email.com", "location": "Denver", "phone": "555-1234"}'
];

const xmlData = [
"<record><name>Chris Lee</name><age>33</age><email>chris@email.com</email><city>Portland</city></record>",
"<record><name>Lisa Wang</name><age>27</age><email>lisa@email.com</email><city>Austin</city></record>",
"<record><name>David Kim</name><age>29</age><email>david@email.com</email><city>Phoenix</city></record>"
];

console.log(`✅ Prepared test data - CSV: ${csvData.length}, JSON: ${jsonData.length}, XML: ${xmlData.length}\n`);

console.log("=".repeat(60) + "\n");

console.log("1. Processing CSV Data:");
console.log("-".repeat(23));

const csvProcessor = new CSVDataProcessor();
const csvResults = csvProcessor.processData(csvData);

console.log("=".repeat(60) + "\n");

console.log("2. Processing JSON Data:");
console.log("-".repeat(24));

const jsonProcessor = new JSONDataProcessor();
const jsonResults = jsonProcessor.processData(jsonData);

console.log("=".repeat(60) + "\n");

console.log("3. Processing XML Data:");
console.log("-".repeat(23));

const xmlProcessor = new XMLDataProcessor();
const xmlResults = xmlProcessor.processData(xmlData);

console.log("=".repeat(60) + "\n");

console.log("Final Results Summary:");
console.log("-".repeat(22));

console.log(`📊 Processing Summary:`);
console.log(` • CSV Processor: ${csvResults ? csvResults.length : 0} records processed`);
console.log(` • JSON Processor: ${jsonResults ? jsonResults.length : 0} records processed`);
console.log(` • XML Processor: ${xmlResults ? xmlResults.length : 0} records processed`);

const totalRecords = (csvResults?.length || 0) + (jsonResults?.length || 0) + (xmlResults?.length || 0);
console.log(` • Total: ${totalRecords} records processed across all formats`);

🌟 Real-World Example

Game Level Generator

// Abstract Level Generator Template
class LevelGenerator {
constructor() {
if (this.constructor === LevelGenerator) {
throw new Error("LevelGenerator is abstract");
}
}

// Template method
generateLevel(seed, difficulty) {
console.log(`🎮 Generating ${this.constructor.name.replace('Generator', '')} level...\n`);

// Step 1: Initialize random generator
this.initializeRandom(seed);

// Step 2: Set difficulty parameters
const params = this.setupDifficultyParameters(difficulty);

// Step 3: Generate terrain (abstract)
const terrain = this.generateTerrain(params);

// Step 4: Place obstacles (abstract)
const obstacles = this.placeObstacles(terrain, params);

// Step 5: Add enemies (abstract)
const enemies = this.addEnemies(terrain, params);

// Step 6: Place collectibles (hook method)
const collectibles = this.placeCollectibles(terrain, params);

// Step 7: Add special features (hook method)
const specialFeatures = this.addSpecialFeatures(terrain, params);

// Step 8: Validate level (concrete method)
const level = this.validateAndFinalize(terrain, obstacles, enemies, collectibles, specialFeatures, params);

console.log("✅ Level generation completed\n");
return level;
}

// Concrete method
initializeRandom(seed) {
this.seed = seed;
this.random = this.createSeededRandom(seed);
console.log(`🎲 Random generator initialized with seed: ${seed}`);
}

createSeededRandom(seed) {
// Simple seeded random number generator
let m = 0x80000000; // 2**31
let a = 1103515245;
let c = 12345;
let state = seed;

return () => {
state = (a * state + c) % m;
return state / (m - 1);
};
}

// Concrete method
setupDifficultyParameters(difficulty) {
console.log(`⚙️ Setting up parameters for ${difficulty} difficulty`);

const baseParams = {
easy: { enemyCount: 5, obstaclePercent: 0.1, collectibleCount: 15 },
medium: { enemyCount: 10, obstaclePercent: 0.2, collectibleCount: 10 },
hard: { enemyCount: 20, obstaclePercent: 0.3, collectibleCount: 5 }
};

const params = baseParams[difficulty] || baseParams.medium;
params.difficulty = difficulty;
params.width = 100;
params.height = 100;

console.log(` Enemy count: ${params.enemyCount}`);
console.log(` Obstacle coverage: ${(params.obstaclePercent * 100)}%`);
console.log(` Collectibles: ${params.collectibleCount}`);

return params;
}

// Abstract methods - must be implemented
generateTerrain(params) {
throw new Error("generateTerrain() must be implemented");
}

placeObstacles(terrain, params) {
throw new Error("placeObstacles() must be implemented");
}

addEnemies(terrain, params) {
throw new Error("addEnemies() must be implemented");
}

// Hook methods - optional to override
placeCollectibles(terrain, params) {
console.log("💎 Placing default collectibles");

const collectibles = [];
for (let i = 0; i < params.collectibleCount; i++) {
collectibles.push({
type: 'coin',
x: Math.floor(this.random() * params.width),
y: Math.floor(this.random() * params.height),
value: 10
});
}

console.log(` Placed ${collectibles.length} coins`);
return collectibles;
}

addSpecialFeatures(terrain, params) {
console.log("✨ No special features added (default)");
return [];
}

// Concrete method
validateAndFinalize(terrain, obstacles, enemies, collectibles, specialFeatures, params) {
console.log("🔍 Validating level...");

const level = {
seed: this.seed,
difficulty: params.difficulty,
type: this.constructor.name.replace('Generator', '').toLowerCase(),
dimensions: { width: params.width, height: params.height },
terrain,
obstacles,
enemies,
collectibles,
specialFeatures,
metadata: {
generated: new Date(),
enemyCount: enemies.length,
obstacleCount: obstacles.length,
collectibleCount: collectibles.length,
specialFeatureCount: specialFeatures.length
}
};

// Validate level has start and end points
level.startPoint = { x: 0, y: 0 };
level.endPoint = { x: params.width - 1, y: params.height - 1 };

console.log("✅ Level validation passed");
console.log(` Dimensions: ${level.dimensions.width}x${level.dimensions.height}`);
console.log(` Elements: ${level.metadata.enemyCount} enemies, ${level.metadata.obstacleCount} obstacles, ${level.metadata.collectibleCount} collectibles`);

return level;
}
}

// Concrete implementation - Forest Level
class ForestLevelGenerator extends LevelGenerator {
generateTerrain(params) {
console.log("🌲 Generating forest terrain");

const terrain = [];
for (let y = 0; y < params.height; y++) {
terrain[y] = [];
for (let x = 0; x < params.width; x++) {
const tileType = this.random() < 0.7 ? 'grass' :
this.random() < 0.8 ? 'dirt' : 'water';
terrain[y][x] = {
type: tileType,
walkable: tileType !== 'water',
x, y
};
}
}

console.log(" Generated forest terrain with grass, dirt, and water tiles");
return terrain;
}

placeObstacles(terrain, params) {
console.log("🌳 Placing forest obstacles (trees, rocks)");

const obstacles = [];
const targetCount = Math.floor(params.width * params.height * params.obstaclePercent);

for (let i = 0; i < targetCount; i++) {
const x = Math.floor(this.random() * params.width);
const y = Math.floor(this.random() * params.height);

if (terrain[y][x].walkable) {
const obstacleType = this.random() < 0.7 ? 'tree' : 'rock';
obstacles.push({
type: obstacleType,
x, y,
blocking: true,
health: obstacleType === 'tree' ? 3 : 5
});
terrain[y][x].walkable = false;
}
}

console.log(` Placed ${obstacles.length} forest obstacles`);
return obstacles;
}

addEnemies(terrain, params) {
console.log("🐻 Adding forest enemies (bears, wolves)");

const enemies = [];
const enemyTypes = ['bear', 'wolf', 'spider'];

for (let i = 0; i < params.enemyCount; i++) {
let placed = false;
let attempts = 0;

while (!placed && attempts < 50) {
const x = Math.floor(this.random() * params.width);
const y = Math.floor(this.random() * params.height);

if (terrain[y][x].walkable) {
const enemyType = enemyTypes[Math.floor(this.random() * enemyTypes.length)];
enemies.push({
type: enemyType,
x, y,
health: enemyType === 'bear' ? 30 : enemyType === 'wolf' ? 20 : 10,
damage: enemyType === 'bear' ? 8 : enemyType === 'wolf' ? 5 : 3,
speed: enemyType === 'spider' ? 3 : enemyType === 'wolf' ? 2 : 1
});
placed = true;
}
attempts++;
}
}

console.log(` Added ${enemies.length} forest creatures`);
return enemies;
}

placeCollectibles(terrain, params) {
console.log("🍄 Placing forest collectibles (mushrooms, berries)");

const collectibles = [];
const collectibleTypes = [
{ type: 'mushroom', value: 15, rarity: 0.3 },
{ type: 'berries', value: 8, rarity: 0.5 },
{ type: 'healing_herb', value: 25, rarity: 0.2 }
];

for (let i = 0; i < params.collectibleCount; i++) {
const x = Math.floor(this.random() * params.width);
const y = Math.floor(this.random() * params.height);

if (terrain[y][x].walkable) {
const roll = this.random();
let selectedType = collectibleTypes[0];

for (const type of collectibleTypes) {
if (roll <= type.rarity) {
selectedType = type;
break;
}
}

collectibles.push({
type: selectedType.type,
x, y,
value: selectedType.value
});
}
}

console.log(` Placed ${collectibles.length} forest collectibles`);
return collectibles;
}

addSpecialFeatures(terrain, params) {
console.log("🏛️ Adding forest special features (ancient shrine)");

const features = [];

// Add an ancient shrine for hard difficulty
if (params.difficulty === 'hard') {
const shrineX = Math.floor(params.width / 2);
const shrineY = Math.floor(params.height / 2);

features.push({
type: 'ancient_shrine',
x: shrineX,
y: shrineY,
effect: 'heal_all',
description: 'An ancient shrine that fully heals the player'
});

console.log(" Added ancient healing shrine");
}

// Add hidden treasure for medium/hard
if (params.difficulty !== 'easy') {
const treasureX = Math.floor(this.random() * params.width);
const treasureY = Math.floor(this.random() * params.height);

features.push({
type: 'hidden_treasure',
x: treasureX,
y: treasureY,
effect: 'gold_bonus',
value: params.difficulty === 'hard' ? 1000 : 500,
description: 'A hidden treasure chest'
});

console.log(" Added hidden treasure chest");
}

return features;
}
}

// Concrete implementation - Dungeon Level
class DungeonLevelGenerator extends LevelGenerator {
generateTerrain(params) {
console.log("🏰 Generating dungeon terrain");

const terrain = [];
for (let y = 0; y < params.height; y++) {
terrain[y] = [];
for (let x = 0; x < params.width; x++) {
const tileType = this.random() < 0.6 ? 'floor' : 'wall';
terrain[y][x] = {
type: tileType,
walkable: tileType === 'floor',
x, y
};
}
}

// Create corridors
this.createCorridors(terrain, params);

console.log(" Generated dungeon with rooms and corridors");
return terrain;
}

createCorridors(terrain, params) {
// Simple corridor creation - horizontal and vertical paths
const midY = Math.floor(params.height / 2);
const midX = Math.floor(params.width / 2);

// Horizontal corridor
for (let x = 0; x < params.width; x++) {
terrain[midY][x] = { type: 'floor', walkable: true, x, y: midY };
}

// Vertical corridor
for (let y = 0; y < params.height; y++) {
terrain[y][midX] = { type: 'floor', walkable: true, x: midX, y };
}
}

placeObstacles(terrain, params) {
console.log("⛩️ Placing dungeon obstacles (pillars, traps)");

const obstacles = [];
const targetCount = Math.floor(params.width * params.height * params.obstaclePercent);

for (let i = 0; i < targetCount; i++) {
const x = Math.floor(this.random() * params.width);
const y = Math.floor(this.random() * params.height);

if (terrain[y][x].walkable) {
const obstacleType = this.random() < 0.5 ? 'pillar' : 'spike_trap';
obstacles.push({
type: obstacleType,
x, y,
blocking: obstacleType === 'pillar',
damage: obstacleType === 'spike_trap' ? 10 : 0
});

if (obstacleType === 'pillar') {
terrain[y][x].walkable = false;
}
}
}

console.log(` Placed ${obstacles.length} dungeon obstacles`);
return obstacles;
}

addEnemies(terrain, params) {
console.log("💀 Adding dungeon enemies (skeletons, goblins)");

const enemies = [];
const enemyTypes = ['skeleton', 'goblin', 'orc'];

for (let i = 0; i < params.enemyCount; i++) {
let placed = false;
let attempts = 0;

while (!placed && attempts < 50) {
const x = Math.floor(this.random() * params.width);
const y = Math.floor(this.random() * params.height);

if (terrain[y][x].walkable) {
const enemyType = enemyTypes[Math.floor(this.random() * enemyTypes.length)];
enemies.push({
type: enemyType,
x, y,
health: enemyType === 'orc' ? 40 : enemyType === 'goblin' ? 15 : 25,
damage: enemyType === 'orc' ? 12 : enemyType === 'goblin' ? 4 : 8,
armor: enemyType === 'skeleton' ? 3 : enemyType === 'orc' ? 2 : 0
});
placed = true;
}
attempts++;
}
}

console.log(` Added ${enemies.length} dungeon monsters`);
return enemies;
}

placeCollectibles(terrain, params) {
console.log("💰 Placing dungeon collectibles (gold, gems)");

const collectibles = [];
const collectibleTypes = [
{ type: 'gold_pile', value: 50, rarity: 0.4 },
{ type: 'ruby', value: 100, rarity: 0.2 },
{ type: 'magic_scroll', value: 75, rarity: 0.3 },
{ type: 'health_potion', value: 40, rarity: 0.1 }
];

for (let i = 0; i < params.collectibleCount; i++) {
const x = Math.floor(this.random() * params.width);
const y = Math.floor(this.random() * params.height);

if (terrain[y][x].walkable) {
const roll = this.random();
let selectedType = collectibleTypes[0];

for (const type of collectibleTypes) {
if (roll <= type.rarity) {
selectedType = type;
break;
}
}

collectibles.push({
type: selectedType.type,
x, y,
value: selectedType.value
});
}
}

console.log(` Placed ${collectibles.length} dungeon treasures`);
return collectibles;
}

addSpecialFeatures(terrain, params) {
console.log("🗝️ Adding dungeon special features");

const features = [];

// Always add a boss room in dungeons
const bossX = Math.floor(this.random() * params.width);
const bossY = Math.floor(this.random() * params.height);

features.push({
type: 'boss_room',
x: bossX,
y: bossY,
effect: 'spawn_boss',
description: 'A dark chamber where the dungeon boss awaits'
});

// Add teleportation circles for medium/hard
if (params.difficulty !== 'easy') {
const teleCount = params.difficulty === 'hard' ? 3 : 2;

for (let i = 0; i < teleCount; i++) {
features.push({
type: 'teleporter',
x: Math.floor(this.random() * params.width),
y: Math.floor(this.random() * params.height),
effect: 'teleport',
targetId: i,
description: `Magical teleportation circle #${i + 1}`
});
}

console.log(` Added boss room and ${teleCount} teleporters`);
} else {
console.log(" Added boss room");
}

return features;
}
}

// Usage
console.log("\n=== Game Level Generator Template Method Demo ===\n");

console.log("Generating different level types:");
console.log("-".repeat(35));

const seed = 12345;
const difficulties = ['easy', 'medium', 'hard'];

console.log("1. Forest Levels:");
console.log("-".repeat(16));

const forestGenerator = new ForestLevelGenerator();

difficulties.forEach((difficulty, index) => {
console.log(`\n${index + 1}.${index + 1} Forest Level - ${difficulty.toUpperCase()}:`);
console.log("-".repeat(30));

const forestLevel = forestGenerator.generateLevel(seed + index, difficulty);

console.log(`📋 Generated Level Summary:`);
console.log(` Type: ${forestLevel.type}`);
console.log(` Seed: ${forestLevel.seed}`);
console.log(` Size: ${forestLevel.dimensions.width}x${forestLevel.dimensions.height}`);
console.log(` Enemies: ${forestLevel.metadata.enemyCount}`);
console.log(` Obstacles: ${forestLevel.metadata.obstacleCount}`);
console.log(` Collectibles: ${forestLevel.metadata.collectibleCount}`);
console.log(` Special Features: ${forestLevel.metadata.specialFeatureCount}`);
});

console.log("\n" + "=".repeat(70) + "\n");

console.log("2. Dungeon Levels:");
console.log("-".repeat(17));

const dungeonGenerator = new DungeonLevelGenerator();

difficulties.forEach((difficulty, index) => {
console.log(`\n${index + 1}.${index + 1} Dungeon Level - ${difficulty.toUpperCase()}:`);
console.log("-".repeat(32));

const dungeonLevel = dungeonGenerator.generateLevel(seed + index + 100, difficulty);

console.log(`📋 Generated Level Summary:`);
console.log(` Type: ${dungeonLevel.type}`);
console.log(` Seed: ${dungeonLevel.seed}`);
console.log(` Size: ${dungeonLevel.dimensions.width}x${dungeonLevel.dimensions.height}`);
console.log(` Enemies: ${dungeonLevel.metadata.enemyCount}`);
console.log(` Obstacles: ${dungeonLevel.metadata.obstacleCount}`);
console.log(` Collectibles: ${dungeonLevel.metadata.collectibleCount}`);
console.log(` Special Features: ${dungeonLevel.metadata.specialFeatureCount}`);
});

console.log("\n" + "=".repeat(70) + "\n");

console.log("Level Generation Performance Summary:");
console.log("-".repeat(38));

const startTime = Date.now();

// Generate batch of levels
const batchResults = [];
for (let i = 0; i < 6; i++) {
const generator = i % 2 === 0 ? new ForestLevelGenerator() : new DungeonLevelGenerator();
const difficulty = difficulties[i % 3];
const level = generator.generateLevel(seed + i + 1000, difficulty);

batchResults.push({
type: level.type,
difficulty: level.difficulty,
elements: level.metadata.enemyCount + level.metadata.obstacleCount + level.metadata.collectibleCount
});
}

const endTime = Date.now();
const totalTime = endTime - startTime;

console.log(`⏱️ Batch Generation Results:`);
console.log(` Total Levels Generated: ${batchResults.length}`);
console.log(` Total Time: ${totalTime}ms`);
console.log(` Average Time per Level: ${(totalTime / batchResults.length).toFixed(1)}ms`);

batchResults.forEach((result, index) => {
console.log(` Level ${index + 1}: ${result.type} (${result.difficulty}) - ${result.elements} elements`);
});

✅ Pros

  • Code Reuse: Common algorithm structure is reused across subclasses
  • Consistency: Ensures all implementations follow the same algorithm steps
  • Extensibility: Easy to add new variations by subclassing
  • Control: Superclass controls the algorithm flow
  • Flexibility: Subclasses can customize specific steps

❌ Cons

  • Inheritance Dependency: Requires inheritance relationship
  • Limited Flexibility: Algorithm structure is fixed
  • Complex Hierarchies: Can lead to complex inheritance hierarchies
  • Debugging Difficulty: Algorithm flow spans multiple classes

🎯 When to Use

  • Similar Algorithms: Multiple classes implement similar algorithms with slight variations
  • Code Duplication: Want to eliminate duplicate algorithm structure
  • Step Customization: Need to customize specific steps while keeping overall structure
  • Framework Development: Building frameworks where users extend base behavior
  • Workflow Processing: Multi-step processes with customizable steps

🔄 Template Method Variations

1. Hook Methods

class TemplateWithHooks {
templateMethod() {
this.stepOne();

if (this.shouldExecuteStepTwo()) {
this.stepTwo();
}

this.stepThree();
}

// Hook method - default implementation
shouldExecuteStepTwo() {
return true;
}
}

2. Strategy-Template Hybrid

class StrategyTemplate {
constructor(strategy) {
this.strategy = strategy;
}

templateMethod() {
this.beforeProcess();
this.strategy.execute();
this.afterProcess();
}
}

3. Functional Template Method

class FunctionalTemplate {
templateMethod(stepImplementations) {
this.stepOne();
stepImplementations.customStep();
this.stepThree();
}
}
  • Strategy: Both define algorithm families, but Template Method uses inheritance while Strategy uses composition
  • Factory Method: Often used within Template Method to create objects
  • Command: Template steps can be implemented as commands
  • Decorator: Can be used to add behavior to template steps

📚 Further Reading