Template Method & Command
Two patterns often grouped together because they both deal with encapsulating behaviour — Template Method fixes the skeleton of an algorithm while letting subclasses fill in the details, and Command turns a request into a first-class object that can be stored, queued, and undone. Understanding both patterns sharpens your ability to design extensible, decoupled systems.
Template Method Pattern
Template Method is a behavioural pattern that defines the overall steps of an algorithm in a base class, then lets subclasses override individual steps without changing the overall structure. The invariant part lives in the abstract superclass; the variant parts are hook or abstract methods.
Core idea: "Don't call us, we'll call you." The base class controls the flow; subclasses only provide the pieces that differ. This is the Hollywood Principle applied to inheritance.
Anatomy of the pattern
- Abstract class — declares the
templateMethod() (usually final) and the abstract/hook steps.
- Concrete class — extends the abstract class and implements only the variable steps.
Real-world example: data exporters
Imagine a reporting system where every export follows the same lifecycle: open connection → fetch data → format data → write output → close connection. The connection and close logic is identical across all formats; only fetch and format differ.
// Abstract base — defines the invariant algorithm skeleton
public abstract class DataExporter {
// Template method — final so subclasses cannot reorder steps
public final void export(String destination) {
openConnection();
List<DataRecord> data = fetchData();
String formatted = formatData(data);
writeOutput(formatted, destination);
closeConnection();
}
// Invariant steps — shared implementation
private void openConnection() {
System.out.println("Opening data source connection");
}
private void closeConnection() {
System.out.println("Closing connection");
}
// Variant steps — subclasses must implement
protected abstract List<DataRecord> fetchData();
protected abstract String formatData(List<DataRecord> data);
// Optional hook — subclasses may override to customise write behaviour
protected void writeOutput(String content, String dest) {
System.out.printf("Writing %d bytes to %s%n", content.length(), dest);
}
}
// Concrete exporter for CSV
public class CsvExporter extends DataExporter {
@Override
protected List<DataRecord> fetchData() {
return List.of(new DataRecord("Alice", 1), new DataRecord("Bob", 2));
}
@Override
protected String formatData(List<DataRecord> rows) {
StringBuilder sb = new StringBuilder("name,id\n");
rows.forEach(r -> sb.append(r.name()).append(",").append(r.id()).append("\n"));
return sb.toString();
}
}
// Concrete exporter for JSON
public class JsonExporter extends DataExporter {
@Override
protected List<DataRecord> fetchData() {
return List.of(new DataRecord("Alice", 1), new DataRecord("Bob", 2));
}
@Override
protected String formatData(List<DataRecord> rows) {
StringBuilder sb = new StringBuilder("[");
rows.forEach(r ->
sb.append("{\"name\":\"").append(r.name())
.append("\",\"id\":").append(r.id()).append("},"));
sb.deleteCharAt(sb.length() - 1);
sb.append("]");
return sb.toString();
}
}
// Usage — the algorithm skeleton never changes, only the concrete type does
DataExporter exporter = new CsvExporter();
exporter.export("/reports/output.csv");
exporter = new JsonExporter();
exporter.export("/reports/output.json");
When to make a step a hook vs. abstract: If a step has a sensible default (e.g. write to stdout), make it a concrete protected method — a hook. Subclasses may override it but are not forced to. If a step has no reasonable default and must vary, declare it abstract. Hooks reduce boilerplate in subclasses that do not need to override every step.
Trade-offs and when to use Template Method
- Pro: Eliminates duplicate algorithm scaffolding across subclasses; changes to the skeleton propagate automatically.
- Pro: Open/Closed Principle — add a new format by adding a class, not by modifying the base.
- Con: Relies on inheritance, which creates tight coupling. If the hierarchy grows deep, it becomes brittle.
- Alternative: When you want the same flexibility without inheritance, use Strategy with composition. Template Method = inheritance; Strategy = delegation.
Command Pattern
Command is a behavioural pattern that encapsulates a request — the action, its target, and its arguments — inside an object. The caller (Invoker) does not know what the command does or how; it only calls execute(). This separation enables queuing, logging, undo/redo, and macro recording.
Participants
- Command interface — declares
execute() and optionally undo().
- ConcreteCommand — binds an action to a Receiver and stores any state needed for undo.
- Receiver — the object that knows how to perform the real work (e.g. a
TextEditor).
- Invoker — stores and calls commands; never depends on concrete types.
- Client — creates ConcreteCommands and wires them to the Invoker.
Real-world example: text editor with undo
// Command interface
public interface EditorCommand {
void execute();
void undo();
}
// Receiver — the object that does the real work
public class TextEditor {
private StringBuilder text = new StringBuilder();
public void insertText(String s, int position) {
text.insert(position, s);
}
public void deleteText(int start, int length) {
text.delete(start, start + length);
}
public String getText() { return text.toString(); }
}
// ConcreteCommand — insert text, stores state required to undo
public class InsertCommand implements EditorCommand {
private final TextEditor editor;
private final String text;
private final int position;
public InsertCommand(TextEditor editor, String text, int position) {
this.editor = editor;
this.text = text;
this.position = position;
}
@Override
public void execute() {
editor.insertText(text, position);
}
@Override
public void undo() {
editor.deleteText(position, text.length());
}
}
// Invoker — maintains command history for undo/redo
public class CommandHistory {
private final Deque<EditorCommand> history = new ArrayDeque<>();
public void executeCommand(EditorCommand cmd) {
cmd.execute();
history.push(cmd);
}
public void undo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
// Client — wires everything together
TextEditor editor = new TextEditor();
CommandHistory history = new CommandHistory();
EditorCommand insertHello = new InsertCommand(editor, "Hello", 0);
EditorCommand insertWorld = new InsertCommand(editor, " World", 5);
history.executeCommand(insertHello); // "Hello"
history.executeCommand(insertWorld); // "Hello World"
System.out.println(editor.getText()); // Hello World
history.undo(); // removes " World"
System.out.println(editor.getText()); // Hello
Lambdas as Commands: When undo() is not required, a @FunctionalInterface command can be replaced by a Runnable or a custom functional interface, and commands are written as lambdas. This is idiomatic in Java 17+ for simple task-queuing scenarios where rollback is not needed.
// Lightweight command queue using lambdas (no undo needed)
Queue<Runnable> commandQueue = new LinkedList<>();
commandQueue.add(() -> System.out.println("Send email"));
commandQueue.add(() -> System.out.println("Generate report"));
while (!commandQueue.isEmpty()) {
commandQueue.poll().run();
}
Advanced use cases
- Macro recording — store a list of commands and replay them all with a single call. Useful for test automation, scripting, and batch processing.
- Transactional rollback — if any command in a sequence fails, call
undo() on all previously executed commands in reverse order.
- Async queues — serialise Command objects and push them to a message broker; a consumer deserialises and executes them, enabling distributed systems.
Undo state bloat: Every Command object must store enough state to reverse its action. In a long-running session with hundreds of undoable commands this can consume significant memory. Cap the history stack size and consider a Memento-based snapshot for complex object graphs instead of per-command state.
Trade-offs
- Pro: Decouples the Invoker from the Receiver completely; either can change independently.
- Pro: Undo/redo, logging, and queuing emerge naturally from storing command objects.
- Con: More classes — one ConcreteCommand per operation. In large applications this can be mitigated by lambdas or generic commands with method references.
Template Method vs. Command — Choosing the right pattern
- Use Template Method when you have a fixed algorithm lifecycle and the only variation is in individual steps. The caller and the concrete class share the same class hierarchy.
- Use Command when you need to parameterise an invoker with an operation, support undo, queue work, or log history. The caller and the action are completely decoupled.
- The two patterns compose well: a
CommandHistory invoker can call template-method-based commands, letting each command encapsulate its own algorithm skeleton.
Summary
Template Method uses inheritance to lock the algorithm structure and delegates variable steps to subclasses via abstract and hook methods — mark the template final to prevent accidental reordering. Command uses composition to turn actions into objects — keep each ConcreteCommand small, store only the state needed to undo, and prefer lambdas when undo is unnecessary. Both patterns are direct applications of the Open/Closed Principle and appear throughout production Java codebases: Template Method in frameworks (AbstractList, Spring's JdbcTemplate), Command in GUI toolkits, job schedulers, and transactional systems.