Card 03/ 09
Command Template in Python, Java and TypeScript
A command with execute() and undo(), an invoker that keeps a history, and one undo.
The Command template is the pattern in its pure form, with the participant names from the class diagram. An invoker runs two commands that add to a receiver's value, then undoes the last one. The steps add each participant in Python, and the tabs give the complete program in three languages, each with its output.
Building the template one participant at a time
Receiver
Holds the state and does the work. Here the work is a number that changes.
class Receiver:
def __init__(self) -> None:
self.value = 0Command
Declares how to carry out a request, and how to reverse it.
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...Concrete command
Holds the receiver and the request's argument, which is all its undo needs.
def execute(self) -> None:
self._receiver.value += self._amount
def undo(self) -> None:
self._receiver.value -= self._amountInvoker
Runs each command and keeps it, so the last one can be undone.
def run(self, command: Command) -> None:
command.execute()
self._history.append(command)
def undo_last(self) -> None:
self._history.pop().undo()The complete template
from abc import ABC, abstractmethod
class Receiver:
def __init__(self) -> None:
self.value = 0
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...
class AddCommand(Command):
def __init__(self, receiver: Receiver, amount: int) -> None:
self._receiver = receiver
self._amount = amount
def execute(self) -> None:
self._receiver.value += self._amount
def undo(self) -> None:
self._receiver.value -= self._amount
class Invoker:
def __init__(self) -> None:
self._history: list[Command] = []
def run(self, command: Command) -> None:
command.execute()
self._history.append(command)
def undo_last(self) -> None:
self._history.pop().undo()
receiver = Receiver()
invoker = Invoker()
invoker.run(AddCommand(receiver, 5))
print("after add 5:", receiver.value)
invoker.run(AddCommand(receiver, 3))
print("after add 3:", receiver.value)
invoker.undo_last()
print("after undo:", receiver.value)after add 5: 5
after add 3: 8
after undo: 5import java.util.ArrayDeque;
import java.util.Deque;
public class CmTemplate {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Invoker invoker = new Invoker();
invoker.run(new AddCommand(receiver, 5));
System.out.println("after add 5: " + receiver.value);
invoker.run(new AddCommand(receiver, 3));
System.out.println("after add 3: " + receiver.value);
invoker.undoLast();
System.out.println("after undo: " + receiver.value);
}
}
class Receiver {
int value;
}
interface Command {
void execute();
void undo();
}
class AddCommand implements Command {
private final Receiver receiver;
private final int amount;
AddCommand(Receiver receiver, int amount) {
this.receiver = receiver;
this.amount = amount;
}
public void execute() {
receiver.value += amount;
}
public void undo() {
receiver.value -= amount;
}
}
class Invoker {
private final Deque<Command> history = new ArrayDeque<>();
void run(Command command) {
command.execute();
history.push(command);
}
void undoLast() {
history.pop().undo();
}
}after add 5: 5
after add 3: 8
after undo: 5class Receiver {
value = 0;
}
interface Command {
execute(): void;
undo(): void;
}
class AddCommand implements Command {
constructor(
private readonly receiver: Receiver,
private readonly amount: number,
) {}
execute(): void {
this.receiver.value += this.amount;
}
undo(): void {
this.receiver.value -= this.amount;
}
}
class Invoker {
private readonly history: Command[] = [];
run(command: Command): void {
command.execute();
this.history.push(command);
}
undoLast(): void {
this.history.pop()?.undo();
}
}
const receiver = new Receiver();
const invoker = new Invoker();
invoker.run(new AddCommand(receiver, 5));
console.log("after add 5:", receiver.value);
invoker.run(new AddCommand(receiver, 3));
console.log("after add 3:", receiver.value);
invoker.undoLast();
console.log("after undo:", receiver.value);after add 5: 5
after add 3: 8
after undo: 5The value goes to 5, then 8, and the undo takes it back to 5. The invoker never mentions AddCommand or Receiver; a second kind of command would join the same history. Each AddCommand can undo itself because subtracting its amount exactly reverses adding it. A command whose change cannot be reversed from its arguments must store more than that.
← → move through sections and cards