Behavioral Pattern Pattern 22 of 23

Template Method

Importing a CSV file and importing a JSON file both boil down to "read the data, process it into a common shape, save the results" — only the middle step actually differs. The Template Method pattern handles this by fixing the overall sequence of steps in a base class once, and letting subclasses override only the specific steps that vary, so the shared steps never get copy-pasted.

⚠️ The Problem

Say you're writing data importers for a few file formats. A CsvImporter needs to: read the raw file, validate it's well-formed CSV, transform rows into your internal record shape, then save the records. A JsonImporter needs to do the exact same four steps — read, validate, transform, save — but the validate and transform steps work completely differently for JSON than for CSV. If you write each importer as an unrelated class with its own import() method from scratch, you end up copy-pasting the "read the file" and "save the records" steps into every single importer, and if the saving logic ever needs to change (say, batching writes), you have to remember to update it identically in every importer class that exists.

✅ The Solution

Put the fixed sequence — read, validate, transform, save — into a single import() method on a base class, and mark it so it can't be overridden (e.g. final in Java, sealed in C#). That method calls out to a couple of abstract "hook" methods, like validate() and transform(), for exactly the steps that vary per format. CsvImporter and JsonImporter subclass the base and override only those two hooks — the shared readData() and save() steps live once, in the base class, and every subclass automatically gets them for free. The overall shape of the algorithm can never accidentally drift between subclasses, because subclasses can't touch it.

Example 1 — a data import pipeline

Primary Example — Java
abstract class DataImporter {

    // The template method — fixed sequence, cannot be overridden.
    public final void importFile(String path) {
        String raw = readData(path);
        if (!validate(raw)) {
            throw new IllegalArgumentException("Invalid file format: " + path);
        }
        List<Record> records = transform(raw);
        save(records);
    }

    private String readData(String path) {
        System.out.println("Reading raw bytes from " + path);
        return "...raw file contents...";
    }

    private void save(List<Record> records) {
        System.out.println("Saving " + records.size() + " records to the database.");
    }

    // Hooks — each subclass fills these in for its own format.
    protected abstract boolean validate(String raw);
    protected abstract List<Record> transform(String raw);
}

class CsvImporter extends DataImporter {
    protected boolean validate(String raw) {
        System.out.println("Validating CSV structure (commas, header row)...");
        return true;
    }
    protected List<Record> transform(String raw) {
        System.out.println("Splitting CSV rows into Record objects...");
        return List.of(new Record("csv-row-1"));
    }
}

class JsonImporter extends DataImporter {
    protected boolean validate(String raw) {
        System.out.println("Validating JSON structure (matching braces, valid keys)...");
        return true;
    }
    protected List<Record> transform(String raw) {
        System.out.println("Parsing JSON objects into Record objects...");
        return List.of(new Record("json-object-1"));
    }
}

class Record {
    Record(String id) {}
}

// Usage:
new CsvImporter().importFile("customers.csv");
new JsonImporter().importFile("customers.json");

importFile() is final — no subclass can change the order of read → validate → transform → save, or skip a step. CsvImporter and JsonImporter only ever touch validate() and transform(), which is exactly the part that's actually different between them.

Example 2 — a secondary context: a game AI turn sequence

Secondary Example — C#
public abstract class Unit
{
    // Template method — the fixed turn sequence.
    public void TakeTurn()
    {
        StartTurn();
        Move();
        Attack();
        EndTurn();
    }

    private void StartTurn() => Console.WriteLine($"{GetType().Name} starts its turn.");
    private void EndTurn() => Console.WriteLine($"{GetType().Name} ends its turn.\n");

    protected abstract void Move();
    protected abstract void Attack();
}

public class Archer : Unit
{
    protected override void Move() => Console.WriteLine("Archer repositions to high ground.");
    protected override void Attack() => Console.WriteLine("Archer fires an arrow from range.");
}

public class Berserker : Unit
{
    protected override void Move() => Console.WriteLine("Berserker charges straight at the enemy.");
    protected override void Attack() => Console.WriteLine("Berserker swings a greataxe in melee.");
}

// Usage
var units = new List<Unit> { new Archer(), new Berserker() };
foreach (var unit in units)
{
    unit.TakeTurn();
}

Every unit type follows the exact same start → move → attack → end sequence — that's fixed on Unit — but Move() and Attack() look completely different per subclass. Adding a Mage unit later means writing those two methods, not re-implementing the whole turn sequence.

👍 When To Use It

  • Several classes implement the same overall multi-step process, and only a subset of the steps actually vary between them.
  • You want to guarantee the overall sequence can never be broken or reordered by a subclass, only specific steps customized.
  • You're seeing near-duplicate methods across classes that differ in only one or two internal steps.

👎 When NOT To Use It

  • When the "shared steps" aren't actually stable — if the overall sequence itself needs to vary per subclass too, forcing everything through one fixed base-class method fights the real requirements.
  • When you'd rather compose behavior than inherit it — Template Method relies on inheritance, which ties subclasses to the base class permanently; Strategy (composition) is often more flexible if that coupling is a concern.
  • When there's only one implementation and no second one is realistically coming — the abstract base class and hook methods are pure ceremony until a second variant actually shows up.

❓ FAQ

How is Template Method different from Strategy?

Template Method uses inheritance: the overall algorithm's shape lives once in a base class as a fixed (often final/sealed) method, and subclasses override only specific steps of it — the skeleton itself is never swapped out, only filled in differently. Strategy uses composition: the entire algorithm is extracted into its own interchangeable object, passed into a context from outside, with no shared base-class skeleton dictating a sequence of steps at all — swapping strategies means handing in a completely different object, not overriding a hook. Rule of thumb: if you're overriding a couple of steps of an otherwise-fixed sequence defined in a base class, that's Template Method; if you're handing in one interchangeable "do the whole thing" object with no shared base class involved, that's Strategy.

Why mark the template method final/sealed — what goes wrong if I don't?

If a subclass can override the template method itself, it can silently change or skip steps of the "fixed" sequence — reordering them, dropping the save step, whatever it wants — and the whole point of guaranteeing a consistent process falls apart. Marking it final/sealed (where the language supports it) makes that impossible at compile time; in languages without that keyword (e.g. plain JavaScript/Python), it becomes a documented convention instead of an enforced one, so code review has to catch any accidental override.

Can a hook method have a default implementation instead of being fully abstract?

Yes — these are sometimes called "hooks" specifically to distinguish them from strictly abstract steps. A hook can have a sensible default behavior (or even do nothing) that most subclasses are happy to leave alone, while a subclass that needs different behavior overrides just that hook. This is common for optional steps — e.g. an optional onImportComplete() hook that most importers ignore but one importer overrides to send a notification.

🙋 There Are No Dumb Questions

Q: Isn't this just... normal inheritance and method overriding? What's special about it being a "pattern"?
A: It is just inheritance and overriding — the "pattern" part is the specific discipline of putting the overall algorithm's sequence in exactly one non-overridable method, and making sure subclasses only ever touch the individual step hooks, never the sequence itself. Plenty of inheritance in the wild doesn't follow that discipline (subclasses freely override entire methods, sequence and all) — naming it as a distinct pattern is really about calling out that specific, more disciplined shape as worth reaching for deliberately.

✅ Quick Check

In the Template Method pattern, what are subclasses allowed to change?


Next up: how do you add brand-new operations across a whole family of classes — like shapes or AST nodes — without editing every one of those classes each time? That's the Visitor pattern, the last stop in this track.

← Back to
Next →