🙋🏻♀️ What is the Command Pattern?
The Command Pattern is a behavioral design pattern that provides a way to encapsulate a request as a stand-alone object with all information about the request, promoting decoupling between objects.
In a sense, the Command Pattern is a means of encapsulating a method call, an operation, or a trigger event as an object, called as command
. Thus, you can create a list of actions or tasks that your program will perform later.
command
: an object withexecute()
method, which contains all required information to execute the method.receiver
: an object that knows how to perform the operations associated with carrying out a request. When acommand
is executed, the corresponding operation(s) in thereceiver
is called.invoker
: an object that sends thecommand
to thereceiver
not aware of the specific concrete command it’s invoking, which promotes loose coupling in the system.
✨ Simple TypeScript Example
Let’s start from the Command
interface for all command
s. It mandates a single method, execute()
, which every concrete command class will implement.
interface Command {
execute(): void
}
The receiver
has the actual implementations of the operations to be performed.
class Receiver {
turnOn(): void {
console.log('operations to turn on')
}
turnOff(): void {
console.log('operations to turn off')
}
}
Now you need an invoker
class that stores the command objects and calls their execute()
method when required, and also keeps track of command history for usability.
class Invoker {
commands: { [id: string]: Command }
history: { id: string; timeStamp: number }[]
constructor() {
this.commands = {}
this.history = []
}
register(commandId: string, command: Command): void {
this.commands[commandId] = command
}
execute(commandId: string): void {
const command = this.commands[commandId]
if (command === undefined) {
return console.log(`Command ${commandId} not found`)
}
command.execute()
this.history.push({ id: commandId, timeStamp: Date.now() })
}
redo(): void {
const lastCommandId = this.history[this.history.length - 1].id
this.execute(lastCommandId)
}
}
These are the concrete implementations of the Command interface, encapsulating an operation and its parameters.
class OnCommand implements Command {
receiver: Receiver
constructor(receiver: Receiver) {
this.receiver = receiver
}
execute(): void {
this.receiver.turnOn()
}
}
class OffCommand implements Command {
receiver: Receiver
constructor(receiver: Receiver) {
this.receiver = receiver
}
execute(): void {
this.receiver.turnOff()
}
}
In the client code, commands
are registered with the invoker
const invoker = new Invoker()
const receiver = new Receiver()
invoker.register('TURN_ON', new OnCommand(receiver))
invoker.register('TURN_OFF', new OffCommand(receiver))
Then the invoker
executes these encapsulated commands.
invoker.execute('TURN_ON')
invoker.execute('TURN_OFF')
invoker.execute('TURN_ON')
invoker.execute('TURN_OFF')
invoker.redo()
🧑💻 Use it or Avoid it
When to use it
The Command Pattern is useful in the following scenarios:
- Undo/Redo functionality: By encapsulating operations as commands, it becomes easier to implement undo/redo functionality, as each command can store the necessary information to reverse its execution.
- Queueing and scheduling requests: Commands can be queued or scheduled for execution at specific times or in a specific order.
- Callbacks and event handling: Commands can be used to encapsulate callbacks or event handlers, allowing you to pass around and execute these functions as objects.
- Decoupling request senders from receivers: The Command Pattern decouples the object that invokes the operation from the object that performs the operation. This allows for more flexibility and extensibility, as different commands can be dynamically bound to the sender.
When to avoid it
- If you use it for trivial operations, it might add unnecessary complexity and result in a bloated codebase.
- If you’re using it in a simple application where simple method calls would be more straightforward, the overhead of maintaining Command classes can outweigh the benefits