Command Design Pattern in Swift


Greetings, traveler!

Welcome to our discussion on the Command Design Pattern, a convenient Behavioral Design Pattern in software development. The Command allows us to encapsulate operations in separate objects, making it easier to manage their history, delay their execution, queue them up, and cancel them if necessary.

Example of usage

Let’s understand this concept with a relatable example. Imagine playing a racing video game where you control a car and where you need to determine in advance the actions that will lead to the successful completion of the track.

final class Car {
    
    let model: String
    
    init(model: String) {
        self.model = model
    }
    
}

You can use commands like go forward, go backward, turn right, and turn left.

protocol CarCommand {
    func execute(for car: Car)
}

final class MoveForward: CarCommand {
    func execute(for car: Car) {
        print("The \(car.model) is moving forward")
    }
}

final class TurnLeft: CarCommand {
    func execute(for car: Car) {
        print("The \(car.model) is turning left")
    }
}

final class TurnRight: CarCommand {
    func execute(for car: Car) {
        print("The \(car.model) is turning right")
    }
}

final class MoveBackward: CarCommand {
    func execute(for car: Car) {
        print("The \(car.model) is moving back")
    }
}

This is what our RacingGame class looks like:

final class RacingGame {
    
    private let car: Car
    private var commands: [CarCommand] = []

    init(car: Car) {
        self.car = car
    }
    
    func addCommand(_ command: CarCommand) {
        commands.append(command)
    }

    func execute() {
        commands.forEach {
            $0.execute(for: car)
        }
    }
    
}

Undo Command

Suppose you’ve ever played a modern racing game like Forza Horizon. In that case, you may remember that it’s possible in some of them to rewind the action, allowing you to replay a situation if you made a mistake. In our game prototype, we’ll be adding this feature as well.

final class RacingGame {
    
    private let car: Car
    private var commands: [CarCommand] = []

    init(car: Car) {
        self.car = car
    }
    
    func addCommand(_ command: CarCommand) {
        commands.append(command)
    }

    func execute() {
        commands.forEach {
            $0.execute(for: car)
        }
    }
    
    func rewind() {
        guard !commands.isEmpty else { return }
        commands.removeLast()
    }
    
}

Now we can use our game.

let car = Car(model: "Red Car")
let game = RacingGame(car: car)
let commands: [CarCommand] = [
    MoveForward(),
    MoveForward(),
    TurnLeft(),
    MoveForward(),
    MoveBackward()
]

commands.forEach {
    game.addCommand($0)
}

game.rewind()
game.execute()

Conclusion

We have explored the user-friendly and concise behavioral design pattern — the Command. Like many other patterns, this pattern can complicate the code if misused. However, a reasonable approach can significantly facilitate working with the codebase. The key is to remember when and how to apply it.

We have finished exploring the Command pattern and will move on to the next design pattern — the Iterator.