Command pattern in Swift
In software engineering, the command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.
The command pattern is useful for implementing undo/redo functionality in an application, as it allows the execution of actions to be treated the same way as other objects. This makes it possible to store the actions in a history list and revert to earlier states by executing the stored actions in reverse order.
The command pattern can also be used to implement deferred execution of an operation, or to separate an object that invokes an operation from the object that knows how to perform the operation. This can be useful for decoupling the objects in an application and making the code more modular and extensible.
In the command pattern, the object that invokes the operation is called the invoker, the object that knows how to perform the operation is called the receiver, and the object that represents the action to be performed 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
- 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
Originally published at https://needone.app on December 30, 2022.