Стратегія — це поведінковий патерн, який виносить набір алгоритмів у власні класи і робить їх взаємозамінними.
Інші об’єкти містять посилання на об’єкт-стратегію та делегують їй роботу. Програма може підмінити цей об’єкт іншим, якщо потрібен інший спосіб вирішення завдання.
Концептуальний приклад
Цей приклад показує структуру патерна Стратегія , а саме — з яких класів він складається, які ролі ці класи виконують і як вони взаємодіють один з одним.
Після ознайомлення зі структурою, вам буде легше сприймати наступний приклад, що розглядає реальний випадок використання патерна в світі Swift.
Example.swift: Приклад структури патерна
import XCTest
/// The Context defines the interface of interest to clients.
class Context {
/// 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.
private var strategy: Strategy
/// Usually, the Context accepts a strategy through the constructor, but
/// also provides a setter to change it at runtime.
init(strategy: Strategy) {
self.strategy = strategy
}
/// Usually, the Context allows replacing a Strategy object at runtime.
func update(strategy: Strategy) {
self.strategy = strategy
}
/// The Context delegates some work to the Strategy object instead of
/// implementing multiple versions of the algorithm on its own.
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 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() {
/// 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.
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")