# Template Method Pattern

## Problem

A data mining framework needs to extract data from different file formats (CSV, JSON, database), but the overall process is the same: open source → extract raw data → parse data → analyze data → generate report → close source. Duplicating this skeleton across each format leads to code duplication and inconsistent pipelines.

## Solution

Define the algorithm skeleton in a base class **template method** that calls abstract steps. Subclasses override specific steps without changing the overall structure. Optional **hook methods** let subclasses customize behavior at certain points without being forced to override.

**Key participants:**
- **AbstractClass** — defines the template method and abstract/hook steps
- **ConcreteClass** — implements the abstract steps for a specific data source

## When to Use

- Multiple classes share the same algorithmic structure but differ in specific steps
- You want to control the order of operations while allowing customization
- You want to avoid code duplication across similar algorithms
- Hook methods provide optional extension points

## When to Avoid

- Every step varies — there's no common skeleton to factor out
- You need to swap algorithms at runtime (use Strategy instead)
- The number of steps that vary is large — the base class becomes unwieldy
- Subclass explosion: too many combinations of step implementations

## Pseudocode

```
ABSTRACT CLASS DataMiner:
    // Template method — defines the skeleton
    method mine(path):
        file = open_source(path)
        raw = extract_data(file)
        data = parse_data(raw)
        analysis = analyze_data(data)
        report = generate_report(analysis)
        close_source(file)
        RETURN report

    // Abstract steps (must override)
    ABSTRACT method open_source(path)
    ABSTRACT method extract_data(file)
    ABSTRACT method parse_data(raw)

    // Concrete steps (shared)
    method analyze_data(data):
        RETURN compute_statistics(data)

    // Hook (optional override)
    method generate_report(analysis):
        RETURN default_report(analysis)

CLASS CSVMiner EXTENDS DataMiner:
    method open_source(path): ...
    method extract_data(file): ...
    method parse_data(raw): ...
```

## Python

```python
from __future__ import annotations
from abc import ABC, abstractmethod


class DataMiner(ABC):
    """Base class with template method."""

    def mine(self, path: str) -> str:
        """Template method — fixed algorithm skeleton."""
        print(f"  [1] Opening: {path}")
        source = self.open_source(path)

        print(f"  [2] Extracting raw data...")
        raw = self.extract_data(source)

        print(f"  [3] Parsing data...")
        data = self.parse_data(raw)

        print(f"  [4] Analyzing...")
        analysis = self.analyze_data(data)

        print(f"  [5] Generating report...")
        report = self.generate_report(analysis)

        print(f"  [6] Closing source")
        self.close_source(source)

        self.hook_after_mining()
        return report

    @abstractmethod
    def open_source(self, path: str) -> str:
        pass

    @abstractmethod
    def extract_data(self, source: str) -> str:
        pass

    @abstractmethod
    def parse_data(self, raw: str) -> list[dict]:
        pass

    def analyze_data(self, data: list[dict]) -> dict:
        """Concrete step — shared across all miners."""
        return {
            "record_count": len(data),
            "fields": list(data[0].keys()) if data else [],
        }

    def generate_report(self, analysis: dict) -> str:
        """Default report — can be overridden (hook-like)."""
        return f"Report: {analysis['record_count']} records, fields: {analysis['fields']}"

    def close_source(self, source: str) -> None:
        pass  # default: no-op

    def hook_after_mining(self) -> None:
        """Hook — optional override point."""
        pass


class CSVMiner(DataMiner):
    def open_source(self, path: str) -> str:
        return f"<CSV file handle: {path}>"

    def extract_data(self, source: str) -> str:
        return "name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,Chicago"

    def parse_data(self, raw: str) -> list[dict]:
        lines = raw.strip().split("\n")
        headers = lines[0].split(",")
        return [dict(zip(headers, line.split(","))) for line in lines[1:]]

    def close_source(self, source: str) -> None:
        print(f"    Closed {source}")


class JSONMiner(DataMiner):
    def open_source(self, path: str) -> str:
        return f"<JSON file handle: {path}>"

    def extract_data(self, source: str) -> str:
        return '[{"name":"Alice","age":30},{"name":"Bob","age":25}]'

    def parse_data(self, raw: str) -> list[dict]:
        import json
        return json.loads(raw)

    def generate_report(self, analysis: dict) -> str:
        """Override the report format for JSON."""
        return f"JSON Report: found {analysis['record_count']} objects with keys {analysis['fields']}"

    def hook_after_mining(self) -> None:
        print("    [Hook] JSON mining complete — cache cleared")


class DatabaseMiner(DataMiner):
    def open_source(self, path: str) -> str:
        return f"<DB connection: {path}>"

    def extract_data(self, source: str) -> str:
        return "row1:Alice:30|row2:Bob:25|row3:Charlie:35"

    def parse_data(self, raw: str) -> list[dict]:
        rows = raw.split("|")
        result = []
        for row in rows:
            parts = row.split(":")
            result.append({"id": parts[0], "name": parts[1], "age": parts[2]})
        return result

    def close_source(self, source: str) -> None:
        print(f"    Disconnected from {source}")


def main() -> None:
    miners: list[tuple[str, DataMiner, str]] = [
        ("CSV", CSVMiner(), "data/users.csv"),
        ("JSON", JSONMiner(), "data/users.json"),
        ("Database", DatabaseMiner(), "postgres://localhost/users"),
    ]

    for name, miner, path in miners:
        print(f"\n=== {name} Mining ===")
        report = miner.mine(path)
        print(f"  Result: {report}")


if __name__ == "__main__":
    main()
```

**Output:**
```
=== CSV Mining ===
  [1] Opening: data/users.csv
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    Closed <CSV file handle: data/users.csv>
  Result: Report: 3 records, fields: ['name', 'age', 'city']

=== JSON Mining ===
  [1] Opening: data/users.json
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    [Hook] JSON mining complete — cache cleared
  Result: JSON Report: found 2 objects with keys ['name', 'age']

=== Database Mining ===
  [1] Opening: postgres://localhost/users
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    Disconnected from <DB connection: postgres://localhost/users>
  Result: Report: 3 records, fields: ['id', 'name', 'age']
```

## Go

```go
package main

import (
	"fmt"
	"strings"
)

type Record map[string]string

// MinerSteps defines the customizable steps.
type MinerSteps interface {
	OpenSource(path string) string
	ExtractData(source string) string
	ParseData(raw string) []Record
	CloseSource(source string)
	GenerateReport(analysis map[string]interface{}) string
	HookAfterMining()
}

// Mine is the template method — it calls steps in order.
func Mine(m MinerSteps, path string) string {
	fmt.Printf("  [1] Opening: %s\n", path)
	source := m.OpenSource(path)

	fmt.Println("  [2] Extracting raw data...")
	raw := m.ExtractData(source)

	fmt.Println("  [3] Parsing data...")
	data := m.ParseData(raw)

	fmt.Println("  [4] Analyzing...")
	analysis := analyzeData(data)

	fmt.Println("  [5] Generating report...")
	report := m.GenerateReport(analysis)

	fmt.Println("  [6] Closing source")
	m.CloseSource(source)

	m.HookAfterMining()
	return report
}

func analyzeData(data []Record) map[string]interface{} {
	fields := []string{}
	if len(data) > 0 {
		for k := range data[0] {
			fields = append(fields, k)
		}
	}
	return map[string]interface{}{
		"record_count": len(data),
		"fields":       fields,
	}
}

func defaultReport(analysis map[string]interface{}) string {
	return fmt.Sprintf("Report: %d records, fields: %v",
		analysis["record_count"], analysis["fields"])
}

// --- CSV Miner ---
type CSVMiner struct{}

func (c *CSVMiner) OpenSource(path string) string {
	return fmt.Sprintf("<CSV: %s>", path)
}
func (c *CSVMiner) ExtractData(source string) string {
	return "name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,Chicago"
}
func (c *CSVMiner) ParseData(raw string) []Record {
	lines := strings.Split(strings.TrimSpace(raw), "\n")
	headers := strings.Split(lines[0], ",")
	var records []Record
	for _, line := range lines[1:] {
		vals := strings.Split(line, ",")
		rec := Record{}
		for i, h := range headers {
			rec[h] = vals[i]
		}
		records = append(records, rec)
	}
	return records
}
func (c *CSVMiner) CloseSource(source string) {
	fmt.Printf("    Closed %s\n", source)
}
func (c *CSVMiner) GenerateReport(analysis map[string]interface{}) string {
	return defaultReport(analysis)
}
func (c *CSVMiner) HookAfterMining() {}

// --- JSON Miner ---
type JSONMiner struct{}

func (j *JSONMiner) OpenSource(path string) string {
	return fmt.Sprintf("<JSON: %s>", path)
}
func (j *JSONMiner) ExtractData(source string) string {
	return "name:Alice,age:30|name:Bob,age:25"
}
func (j *JSONMiner) ParseData(raw string) []Record {
	var records []Record
	for _, row := range strings.Split(raw, "|") {
		rec := Record{}
		for _, pair := range strings.Split(row, ",") {
			kv := strings.SplitN(pair, ":", 2)
			rec[kv[0]] = kv[1]
		}
		records = append(records, rec)
	}
	return records
}
func (j *JSONMiner) CloseSource(source string) {}
func (j *JSONMiner) GenerateReport(analysis map[string]interface{}) string {
	return fmt.Sprintf("JSON Report: found %d objects with keys %v",
		analysis["record_count"], analysis["fields"])
}
func (j *JSONMiner) HookAfterMining() {
	fmt.Println("    [Hook] JSON mining complete - cache cleared")
}

func main() {
	miners := []struct {
		name  string
		miner MinerSteps
		path  string
	}{
		{"CSV", &CSVMiner{}, "data/users.csv"},
		{"JSON", &JSONMiner{}, "data/users.json"},
	}

	for _, m := range miners {
		fmt.Printf("\n=== %s Mining ===\n", m.name)
		report := Mine(m.miner, m.path)
		fmt.Printf("  Result: %s\n", report)
	}
}
```

**Output:**
```
=== CSV Mining ===
  [1] Opening: data/users.csv
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    Closed <CSV: data/users.csv>
  Result: Report: 3 records, fields: [name age city]

=== JSON Mining ===
  [1] Opening: data/users.json
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    [Hook] JSON mining complete - cache cleared
  Result: JSON Report: found 2 objects with keys [name age]
```

## JavaScript

```javascript
class DataMiner {
  /** Template method — fixed skeleton. */
  mine(path) {
    console.log(`  [1] Opening: ${path}`);
    const source = this.openSource(path);

    console.log("  [2] Extracting raw data...");
    const raw = this.extractData(source);

    console.log("  [3] Parsing data...");
    const data = this.parseData(raw);

    console.log("  [4] Analyzing...");
    const analysis = this.analyzeData(data);

    console.log("  [5] Generating report...");
    const report = this.generateReport(analysis);

    console.log("  [6] Closing source");
    this.closeSource(source);

    this.hookAfterMining();
    return report;
  }

  // Abstract steps — subclasses MUST override
  openSource(path)     { throw new Error("Must implement openSource"); }
  extractData(source)  { throw new Error("Must implement extractData"); }
  parseData(raw)       { throw new Error("Must implement parseData"); }

  // Concrete step — shared
  analyzeData(data) {
    return {
      recordCount: data.length,
      fields: data.length > 0 ? Object.keys(data[0]) : [],
    };
  }

  // Default implementation — can be overridden
  generateReport(analysis) {
    return `Report: ${analysis.recordCount} records, fields: [${analysis.fields.join(", ")}]`;
  }

  // Hooks — optional
  closeSource(source) {}
  hookAfterMining() {}
}

class CSVMiner extends DataMiner {
  openSource(path) { return `<CSV: ${path}>`; }

  extractData(source) {
    return "name,age,city\nAlice,30,NYC\nBob,25,LA\nCharlie,35,Chicago";
  }

  parseData(raw) {
    const lines = raw.trim().split("\n");
    const headers = lines[0].split(",");
    return lines.slice(1).map(line => {
      const values = line.split(",");
      const record = {};
      headers.forEach((h, i) => record[h] = values[i]);
      return record;
    });
  }

  closeSource(source) {
    console.log(`    Closed ${source}`);
  }
}

class JSONMiner extends DataMiner {
  openSource(path) { return `<JSON: ${path}>`; }

  extractData(source) {
    return '[{"name":"Alice","age":30},{"name":"Bob","age":25}]';
  }

  parseData(raw) {
    return JSON.parse(raw);
  }

  generateReport(analysis) {
    return `JSON Report: found ${analysis.recordCount} objects with keys [${analysis.fields.join(", ")}]`;
  }

  hookAfterMining() {
    console.log("    [Hook] JSON mining complete - cache cleared");
  }
}

// --- Demo ---
const miners = [
  { name: "CSV", miner: new CSVMiner(), path: "data/users.csv" },
  { name: "JSON", miner: new JSONMiner(), path: "data/users.json" },
];

for (const { name, miner, path } of miners) {
  console.log(`\n=== ${name} Mining ===`);
  const report = miner.mine(path);
  console.log(`  Result: ${report}`);
}
```

**Output:**
```
=== CSV Mining ===
  [1] Opening: data/users.csv
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    Closed <CSV: data/users.csv>
  Result: Report: 3 records, fields: [name, age, city]

=== JSON Mining ===
  [1] Opening: data/users.json
  [2] Extracting raw data...
  [3] Parsing data...
  [4] Analyzing...
  [5] Generating report...
  [6] Closing source
    [Hook] JSON mining complete - cache cleared
  Result: JSON Report: found 2 objects with keys [name, age]
```

## Related Patterns

- **Strategy** — prefer Strategy over Template Method when the algorithm needs to be swapped at runtime rather than at subclass definition time; Strategy uses composition, Template Method uses inheritance.
- **Hook Method** — hook methods are the optional override points defined inside a template method; they are part of the same pattern.
- **Factory Method** — factory method is often a step inside a template method, letting subclasses control which objects are created.
