Command Pattern
Introduction
The Command pattern is a behavioral design pattern that turns a request into a stand-alone object that contains all information about the request. This transformation lets you parameterize methods with different requests, delay or queue a request’s execution, and support undoable operations.
Purpose
- To encapsulate a request as an object, thereby allowing users to parameterize clients with queues, requests, and operations.
- To support undoable operations.
- To provide a means to decouple the sender of a request from its receiver.
Use Cases
- When you need to parameterize objects according to an action to perform.
- When you need to queue, log, or undo operations.
- When you want to support rollback operations or transactional behavior.
Advantages/Disadvantages
Advantages:
- Decouples the classes that invoke operations from the classes that perform these operations.
- Provides flexibility in choosing when and how to execute operations.
- Can assemble a set of commands into a complex operation.
Disadvantages:
- Can lead to more complex code due to the addition of new classes.
- Overhead of a command object and extra indirection of executing a command.
Implementation in JavaScript
class Command {
execute() {}
}
class ConcreteCommand extends Command {
constructor(receiver, state) {
super();
this.receiver = receiver;
this.state = state;
}
execute() {
console.log(`Executing command with state: ${this.state}`);
this.receiver.action(this.state);
}
}
class Receiver {
action(state) {
console.log(`Receiver acting on: ${state}`);
}
}
// Usage
const receiver = new Receiver();
const command = new ConcreteCommand(receiver, 'some state');
command.execute();
Practical Example
In a backend scenario, you could have a task scheduling system where tasks are encapsulated as commands, making it easy to manage scheduling, logging, and executing these tasks.
class Task {
constructor(name, payload) {
this.name = name;
this.payload = payload;
}
perform() {
console.log(`Performing task: ${this.name} with payload: ${this.payload}`);
}
}
class TaskCommand {
constructor(task) {
this.task = task;
}
execute() {
this.task.perform();
}
}
// Usage
const task1 = new Task('DatabaseBackup', 'db1');
const taskCommand1 = new TaskCommand(task1);
taskCommand1.execute();
const task2 = new Task('SendEmail', 'user@example.com');
const taskCommand2 = new TaskCommand(task2);
taskCommand2.execute();
In this example, each Task
instance represents a specific task. TaskCommand
encapsulates a task and its execution logic. This approach allows the scheduling and execution of tasks to be managed independently of the task’s specific details.