Стратегия — это поведенческий паттерн, выносит набор алгоритмов в собственные классы и делает их взаимозаменимыми.
Другие объекты содержат ссылку на объект-стратегию и делегируют ей работу. Программа может подменить этот объект другим, если требуется иной способ решения задачи.
Концептуальный пример
Этот пример показывает структуру паттерна Стратегия , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
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: ","))
}
}
/// Интерфейс Стратегии объявляет операции, общие для всех поддерживаемых версий
/// некоторого алгоритма.
///
/// Контекст использует этот интерфейс для вызова алгоритма, определённого
/// Конкретными Стратегиями.
protocol Strategy {
func doAlgorithm<T: Comparable>(_ data: [T]) -> [T]
}
/// Конкретные Стратегии реализуют алгоритм, следуя базовому интерфейсу
/// Стратегии. Этот интерфейс делает их взаимозаменяемыми в Контексте.
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: >)
}
}
/// Давайте посмотрим как всё это будет работать.
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()
}
}
Output.txt: Результат выполнения
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
Пример из реальной жизни
Example.swift: Пример из реальной жизни
import XCTest
class StrategyRealWorld: XCTestCase {
/// This example shows a simple implementation of a list controller that is
/// able to display models from different data sources:
///
/// (MemoryStorage, CoreDataStorage, RealmStorage)
func test() {
let controller = ListController()
let memoryStorage = MemoryStorage<User>()
memoryStorage.add(usersFromNetwork())
clientCode(use: controller, with: memoryStorage)
clientCode(use: controller, with: CoreDataStorage())
clientCode(use: controller, with: RealmStorage())
}
func clientCode(use controller: ListController, with dataSource: DataSource) {
controller.update(dataSource: dataSource)
controller.displayModels()
}
private func usersFromNetwork() -> [User] {
let firstUser = User(id: 1, username: "username1")
let secondUser = User(id: 2, username: "username2")
return [firstUser, secondUser]
}
}
class ListController {
private var dataSource: DataSource?
func update(dataSource: DataSource) {
/// ... resest current states ...
self.dataSource = dataSource
}
func displayModels() {
guard let dataSource = dataSource else { return }
let models = dataSource.loadModels() as [User]
/// Bind models to cells of a list view...
print("\nListController: Displaying models...")
models.forEach({ print($0) })
}
}
protocol DataSource {
func loadModels<T: DomainModel>() -> [T]
}
class MemoryStorage<Model>: DataSource {
private lazy var items = [Model]()
func add(_ items: [Model]) {
self.items.append(contentsOf: items)
}
func loadModels<T: DomainModel>() -> [T] {
guard T.self == User.self else { return [] }
return items as! [T]
}
}
class CoreDataStorage: DataSource {
func loadModels<T: DomainModel>() -> [T] {
guard T.self == User.self else { return [] }
let firstUser = User(id: 3, username: "username3")
let secondUser = User(id: 4, username: "username4")
return [firstUser, secondUser] as! [T]
}
}
class RealmStorage: DataSource {
func loadModels<T: DomainModel>() -> [T] {
guard T.self == User.self else { return [] }
let firstUser = User(id: 5, username: "username5")
let secondUser = User(id: 6, username: "username6")
return [firstUser, secondUser] as! [T]
}
}
protocol DomainModel {
var id: Int { get }
}
struct User: DomainModel {
var id: Int
var username: String
}
Output.txt: Результат выполнения
ListController: Displaying models...
User(id: 1, username: "username1")
User(id: 2, username: "username2")
ListController: Displaying models...
User(id: 3, username: "username3")
User(id: 4, username: "username4")
ListController: Displaying models...
User(id: 5, username: "username5")
User(id: 6, username: "username6")