Skip to content

// Design Patterns · 22

Template Method

Same steps, different detailsleads toBase class fixes the order

Fix the steps of an algorithm, let subclasses fill some in.

How it fits together

Template Method structure«client»ImportJobRun(file)CsvImporterParse()«abstract»ImporterBaseImport() fixed steps# Parse() abstract# OnImported() hookXmlImporterParse()OnImported()Import()
uses / callsextends
  • ImportJob uses / calls ImporterBase (Import())
  • CsvImporter extends ImporterBase
  • XmlImporter extends ImporterBase

Spot it

  • Copy-pasted classes that differ in one step
  • Every importer reads, validates, then saves
  • Subclasses forget to call base.Process()

It costs

Ties variation to inheritance; deep hierarchies get rigid and hard to test.

In code

public abstract class ImporterBase(IRowStore store){    public int Import(Stream input)    {        var valid = Parse(input)            .Where(IsValid)            .ToList();​        store.Save(valid);        OnImported(valid.Count);        return valid.Count;    }​    protected abstract IReadOnlyList<ImportRow> Parse(Stream input);​    protected virtual void OnImported(int count)    {    }​    private static bool IsValid(ImportRow row) => !string.IsNullOrWhiteSpace(row.Sku);}
Go deeperThe full explanation and what to ask for · 1 min readExpand

The base class owns the algorithm and calls out to abstract steps and optional hooks that subclasses supply. The base calls the subclass, not the other way round.

Too many hooks, or a template method that subclasses can override, and the fixed order is no longer fixed. When the varying step is independent of the rest, pass it in as a strategy instead of inheriting.

Frameworks use this shape for long-running workers and authentication handlers: the base class handles start, stop or the request flow, and the subclass fills in one overridable step.

Ask for it

  • template method
  • abstract step
  • hook method
  • base class
  • inversion of control

CsvImporter and XmlImporter duplicate the read-validate-save flow. Extract an abstract ImporterBase with an Import() method that subclasses cannot override. It calls an abstract Parse() step and an optional OnImported() hook, then make both importers derive from it.