Form Template Method

Destination
Symptom

Two or more classes follow the same algorithm outline — same order of steps, same control flow — but each open-codes the whole sequence inline. The structural similarity is invisible: every change to the algorithm risks landing in one class but not the others.

Goal

The algorithm lives once in a superclass Template Method that calls named primitive operations. Subclasses implement only the primitives; algorithmic edits land in one place and apply uniformly.

Before the refactoring

// Two reports follow the same outline but each open-codes the whole algorithm.
class HtmlReport {
generate(data) {
let output = '<html><body>';
output += `<h1>${data.title}</h1>`;
output += '<table>';
for (const row of data.rows) {
output += `<tr><td>${row.name}</td><td>${row.value}</td></tr>`;
}
output += '</table>';
output += '</body></html>';
return output;
}
}
class TextReport {
generate(data) {
let output = '';
output += data.title.toUpperCase() + '\n';
output += '='.repeat(data.title.length) + '\n';
for (const row of data.rows) {
output += row.name + ': ' + row.value + '\n';
}
return output;
}
}

After the refactoring

// The algorithm lives once in Report; subclasses supply the per-format primitives.
class Report {
generate(data) {
let output = this.openSection();
output += this.formatTitle(data.title);
output += this.openRows();
for (const row of data.rows) {
output += this.formatRow(row);
}
output += this.closeRows();
output += this.closeSection();
return output;
}
}
class HtmlReport extends Report {
openSection() { return '<html><body>'; }
closeSection() { return '</body></html>'; }
formatTitle(t) { return `<h1>${t}</h1>`; }
openRows() { return '<table>'; }
closeRows() { return '</table>'; }
formatRow(row) { return `<tr><td>${row.name}</td><td>${row.value}</td></tr>`; }
}
class TextReport extends Report {
openSection() { return ''; }
closeSection() { return ''; }
formatTitle(t) { return t.toUpperCase() + '\n' + '='.repeat(t.length) + '\n'; }
openRows() { return ''; }
closeRows() { return ''; }
formatRow(row) { return row.name + ': ' + row.value + '\n'; }
}
Example source: Illustrative example written for this site, faithful to Kerievsky's pattern shape in Refactoring to Patterns (Addison-Wesley, 2004), chapter 8. The book uses tax-calculator subclasses with a shared compute outline; this JavaScript version uses report generators with a shared layout outline — same shape, same Template Method payoff.
Pressure

When the outline is duplicated across classes, reordering steps, adding a new step, or fixing a control-flow bug requires N synchronized edits. Reviewers must scan all N implementations to confirm the algorithm stayed consistent; subtle drift between siblings is the dominant defect.

Tradeoff

Template Method couples subclasses to the superclass's algorithm shape; subclasses cannot reorder steps or skip them without breaking the contract. Primitives proliferate as the algorithm grows, and each must be implemented in every subclass — adding a new step is a Shotgun Surgery move down the hierarchy.

Relief

Algorithmic invariants live in one place; subclasses focus on what's distinctive about them. Adding a new format is a new subclass implementing the primitives; the algorithm's correctness is verified once and inherited.

Trap

Template Method calcifies the algorithm shape early. When one subclass needs a fundamentally different ordering, the superclass's template no longer fits, and the subclass either fights the framework with hooks/flags or escapes via composition. Strategy is often the better answer when the algorithm itself varies.