Many of us have heard about the Command pattern or used them in the development, which is a behavioral design pattern in which an object is used to encapsulate all of the information required to perform an action or trigger an event at a later time. This information includes, for example, the method name, the object that owns the method and values for the method parameters.
Command pattern is quite useful for implementing undo/redo functionality in a program, and store all of the actions in a history list and possibly to revert to earlier states by executing the stored actions in reverse order. One typical use case is the painting app, where users frequently draw and undo strokes, fill colours and clear them while drawing.
Another scenario that the command pattern can also be used to implement is to defer execution of a certain action. This can be especially useful for decoupling the objects in an application and making the code more modular and extensible.
In the command pattern, the certain operation is triggered by invoker, and receiver knows how to perform that operation, and the object that represents the action to be executed is called the command. The command object contains a reference to the receiver and invokes a method on the receiver with the parameters stored in the command.
Example in Swift
protocol Command {
func execute()
}
class Receiver {
func action() {
print("Performing the requested action")
}
}
class ConcreteCommand: Command {
var receiver: Receiver
init(receiver: Receiver) {
self.receiver = receiver
}
func execute() {
receiver.action()
}
}
class Invoker {
var command: Command
init(command: Command) {
self.command = command
}
func invoke() {
command.execute()
}
}
let receiver = Receiver()
let command = ConcreteCommand(receiver: receiver)
let invoker = Invoker(command: command)
invoker.invoke() // Output: "Performing the requested action"
In this example, the ConcreteCommand
class represents a command that contains a reference to a Receiver
object, and invokes the action()
method of the receiver when the execute()
method is called. The Invoker
class holds a reference to a Command
object and invokes it when the invoke()
method is called. The client creates a Receiver
object, a ConcreteCommand
object that references the receiver, and an Invoker
object that references the command, and then triggers the invoker to execute the command.
Here is another example with 2 commands:
/// The Receiver class
class Calculator {
func add(x: Int, y: Int) -> Int {
return x + y
}
func subtract(x: Int, y: Int) -> Int {
return x - y
}
}
// The Command protocol
protocol Command {
func execute() -> Int
}
// The AddCommand class
class AddCommand: Command {
let receiver: Calculator
let x: Int
let y: Int
init(receiver: Calculator, x: Int, y: Int) {
self.receiver = receiver
self.x = x
self.y = y
}
func execute() -> Int {
return receiver.add(x: x, y: y)
}
}
// The SubtractCommand class
class SubtractCommand: Command {
let receiver: Calculator
let x: Int
let y: Int
init(receiver: Calculator, x: Int, y: Int) {
self.receiver = receiver
self.x = x
self.y = y
}
func execute() -> Int {
return receiver.subtract(x: x, y: y)
}
}
// The Invoker class
class CalculatorInvoker {
var history: [Command] = []
func invoke(command: Command) {
history.append(command)
print(command.execute())
}
func undo() {
guard !history.isEmpty else { return }
history.removeLast()
}
}
// Usage
let calculator = Calculator()
let addCommand = AddCommand(receiver: calculator, x: 2, y: 3)
let subtractCommand = SubtractCommand(receiver: calculator, x: 2, y: 3)
let invoker = CalculatorInvoker()
invoker.invoke(command: addCommand) // prints 5
invoker.invoke(command: subtractCommand) // prints -1
invoker.undo() // removes subtractCommand from history
Summary
- The command pattern is useful for implementing undo/redo functionality, such as painting and un-painting in a painting app
- The command pattern is suitable for deferred action, such as save user's data either locally or remotely
- The command pattern is also suitable for executing global command, such as sending user analytics