Strategy vs Template Method vs Command
Published
All three wrap a piece of behaviour so it can be plugged in somewhere, which is why they get lumped together. Split them into two pairs and they stop blurring.
Strategy vs Template Method is one problem solved two ways. A job has a fixed outline and one step that varies. Template Method puts the outline in a parent class and lets a child class fill in the step: the franchise kitchen manual from abstraction vs interface vs abstract class, where steps one to three are fixed and step four is "your local special". Strategy hands the varying step in as a separate object: choosing car, bike or walk in Maps.
Strategy vs Command is a different question. A strategy is how to do a job. A command is what to do, captured as a thing: the order slip from the LLD course. The waiter writes "table 4, two dosas" on a slip, and that slip can wait in a queue, be read by the kitchen later, be crossed out, or be kept for the bill.
You add an Undo button to the cart. Separately, the cart total can be worked out with a regular, a weekend or a combo pricing rule. Which of these is a Command and which is a Strategy? What does one of them have to remember that the other never does?
Strategy vs Template Method: composition or inheritance#
Here is one job, exporting order rows with a header line, where only the row format varies. First as a template method:
abstract class Exporter {
// The outline is fixed here. Children only fill in row().
export(rows: string[][]) {
return ['# orders', ...rows.map((r) => this.row(r))]
}
protected abstract row(cells: string[]): string
}
class CsvExporter extends Exporter {
protected row(cells: string[]) {
return cells.join(',')
}
}
const rows = [['A17', '120'], ['A18', '40']]
for (const line of new CsvExporter().export(rows)) console.log(line)
// > # orders
// > A17,120
// > A18,40And the same job as a strategy, where the varying step is a function passed in:
type RowFormat = (cells: string[]) => string
function exportReport(rows: string[][], row: RowFormat) {
return ['# orders', ...rows.map(row)]
}
const csv: RowFormat = (cells) => cells.join(',')
const pipes: RowFormat = (cells) => cells.join(' | ')
const rows = [['A17', '120'], ['A18', '40']]
console.log(exportReport(rows, csv)) // [ '# orders', 'A17,120', 'A18,40' ]
console.log(exportReport(rows, pipes)[1]) // A17 | 120Same result. The differences show up later, as the code grows:
- Switching at runtime. A strategy is a value, so the user can pick CSV or pipes from a dropdown. With template method the choice is a class, made when the object is created.
- Two varying steps. Say the header also varies, three ways. Template method needs a subclass per combination (3 row formats × 3 headers = 9 classes). Strategy needs 3 + 3 small functions, combined freely.
- Testing. A strategy function is tested by calling it. A template method's step is
protectedand only reachable through a subclass.
That is the reasoning behind "favour composition over inheritance". Template method is still a
good fit when the outline really is fixed and owned by a framework: test runners calling
beforeEach, the test, then afterEach, or React class components where React calls
render and componentDidMount in an order you do not control.
Strategy vs Command: how, or what#
Now the other pair. Play with the cart below: every button creates a command, and the pricing rule is a strategy. Then press Undo.
- Nothing yet.
The pricing rule changes how the total is worked out. Switching it three times leaves no trace, because nothing happened to the cart. Each command is a record of something that happened, with everything needed to do it again or take it back. So commands can go into a history, and strategies cannot.
interface Command {
execute(): void
undo(): void
}
class Cart {
items: string[] = []
}
class AddItem implements Command {
constructor(private cart: Cart, private item: string) {}
execute() {
this.cart.items.push(this.item)
}
undo() {
this.cart.items.pop()
}
}
class RemoveFirst implements Command {
private removed?: string
constructor(private cart: Cart) {}
execute() {
this.removed = this.cart.items.shift()
}
undo() {
if (this.removed) this.cart.items.unshift(this.removed)
}
}
class History {
private done: Command[] = []
run(cmd: Command) {
cmd.execute()
this.done.push(cmd)
}
undo() {
this.done.pop()?.undo()
}
}
const cart = new Cart()
const history = new History()
history.run(new AddItem(cart, 'Masala dosa'))
history.run(new AddItem(cart, 'Filter coffee'))
history.run(new RemoveFirst(cart))
console.log(cart.items) // [ 'Filter coffee' ]
history.undo()
console.log(cart.items) // [ 'Masala dosa', 'Filter coffee' ]Notice what RemoveFirst keeps: the item it removed. A command remembers enough about this
one time it ran to reverse it. A strategy never remembers anything between calls. You hand it
data, it hands back an answer.
That ability to treat an action as a value is why commands show up everywhere once you know the shape:
- Undo and redo: editors, design tools, the cart above.
- Queues and jobs: a background job is a command written down as data so another process can run it later, and retry it.
- Redux actions:
{ type: 'cart/addItem', item: 'Masala dosa' }is a command as plain data. The reducer is what executes it. - Audit logs: keep every command and you have a history of who did what.
All three side by side#
| Strategy | Template Method | Command | |
|---|---|---|---|
| Answers | How to do this job | Which step differs in a fixed outline | What to do, as a thing |
| Built with | Composition: an object or function passed in | Inheritance: a child overrides a step | An object holding an action and its arguments |
| Chosen | At runtime, by the caller | When the class is picked | Each time something happens |
| Remembers anything | No | No | Yes, enough to undo or replay |
| Can be queued, logged, undone | No | No | Yes |
| Lighter TS form | A function | A function parameter (which makes it Strategy) | A plain data object, or a pair of functions |
A command with no undo, run straight away, is just a callback, and in TypeScript you would pass
() => void. It becomes the Command pattern when you need the action as a value with a life
of its own: stored, sent to another process, run later, retried, or reversed. That is when an
object with execute and undo (or a data shape like a Redux action) earns its keep.
| Choice | What you gain | What you pay | Pick it when |
|---|---|---|---|
| Strategy | Swap the algorithm at runtime, combine several varying steps, test each one alone. | The caller must know the options and pass one in. | Interchangeable ways to do one job: pricing, sorting, formatting, rider assignment. |
| Template Method | The outline is enforced in one place and children cannot reorder it. | One subclass per variation, combinations multiply, and children depend on the parent's internals. | A framework that owns the sequence and gives you hooks to fill in. |
| Command | Actions become values: undo, redo, queues, retries, audit history. | A class or data type per action, and undo logic that must stay correct. | You need to store, delay, send, repeat or reverse what was done. |
protected abstract row(cells: string[]): stringstring[][]['# orders', ...rows.map(row)]private removed?: stringthis.done.pop()?.undo()In the interview#
Checkpoint#
1. What is the main structural difference between Strategy and Template Method?
2. Why can a command be undone when a strategy cannot be?
3. A Redux action like { type: "cart/addItem", item: "Vada" } is closest to which pattern?
- Strategy and Template Method: same problem. Composition (pass it in) vs inheritance (override it).
- Default to Strategy, often a plain function. Template method for framework owned outlines.
- Strategy is how. Command is what, as a thing.
- A command remembers enough to undo or replay itself. A strategy remembers nothing.
- The test: would you ever keep it in a list? Then it is a command.
These three put behaviour in a slot, and I split them into two pairs. Strategy and Template Method solve the same problem, a fixed outline with a step that varies. Template method uses inheritance: the parent owns the sequence and a subclass overrides the step. Strategy uses composition: the step is passed in, often as a plain function, so it can be switched at runtime, combined with other varying steps without a subclass per combination, and tested alone. I default to strategy and use template method when a framework owns the outline. Strategy versus Command is how versus what. A strategy is a reusable way to compute something and remembers nothing. A command is one specific action with its arguments, captured as a value, so it can be stored in an undo history, put on a job queue, retried or logged. If I would ever keep it in a list, it is a command.