Strategy – Design Patterns in Swift

Introduction

The Strategy pattern enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.

Strategy Pattern – UML

Use Strategy Pattern in Swift

The Context defines the interface of interest to clients. The Context maintains a reference to one of the Strategy objects. The Context does not know the concrete class of a strategy. It should work with all strategies via the Strategy interface.

Usually, the Context accepts a strategy through the constructor, but also provides a setter to change it at runtime. The Context allows replacing a Strategy object at runtime.

import XCTest

class Context {

    private var strategy: Strategy

    init(strategy: Strategy) {
        self.strategy = strategy
    }

    func update(strategy: Strategy) {
        self.strategy = strategy
    }

    func doSomeBusinessLogic() {
        print("Context: Sorting data using the strategy (not sure how it'll do it)\n")

        let result = strategy.doAlgorithm(["a", "b", "c", "d", "e"])
        print(result.joined(separator: ","))
    }
}

The Context delegates some work to the Strategy object instead of implementing multiple versions of the algorithm on its own.

The Strategy interface declares operations common to all supported versions of some algorithm.

The Context uses this interface to call the algorithm defined by Concrete Strategies.

protocol Strategy {

    func doAlgorithm<T: Comparable>(_ data: [T]) -> [T]
}

Concrete Strategies implement the algorithm while following the base Strategy interface. The interface makes them interchangeable in the Context.

class ConcreteStrategyA: Strategy {

    func doAlgorithm<T: Comparable>(_ data: [T]) -> [T] {
        return data.sorted()
    }
}

class ConcreteStrategyB: Strategy {

    func doAlgorithm<T: Comparable>(_ data: [T]) -> [T] {
        return data.sorted(by: >)
    }
}

Let’s see how it all works together.

class StrategyConceptual: XCTestCase {

    func test() {

        let context = Context(strategy: ConcreteStrategyA())
        print("Client: Strategy is set to normal sorting.\n")
        context.doSomeBusinessLogic()

        print("\nClient: Strategy is set to reverse sorting.\n")
        context.update(strategy: ConcreteStrategyB())
        context.doSomeBusinessLogic()
    }
}

The client code picks a concrete strategy and passes it to the context. The client should be aware of the differences between strategies in order to make the right choice.

Conclusion

In summary, the Strategy pattern can be recognized by a method that lets nested object do the actual work, as well as the setter that allows replacing that object with a different one. The Strategy pattern is very common in Swift code. It’s often used in various frameworks to provide users a way to change the behavior of a class without extending it.

Leave a Reply

Your email address will not be published. Required fields are marked *