Strategy es un patrón de diseño de comportamiento que convierte un grupo de comportamientos en objetos y los hace intercambiables dentro del objeto de contexto original.
El objeto original, llamado contexto, contiene una referencia a un objeto de estrategia y le delega la ejecución del comportamiento. Para cambiar la forma en que el contexto realiza su trabajo, otros objetos pueden sustituir el objeto de estrategia actualmente vinculado, por otro.
Ejemplo conceptual
Este ejemplo ilustra la estructura del patrón de diseño Strategy y se centra en las siguientes preguntas:
¿De qué clases se compone?
¿Qué papeles juegan esas clases?
¿De qué forma se relacionan los elementos del patrón?
Después de conocer la estructura del patrón, será más fácil comprender el siguiente ejemplo basado en un caso de uso real de Swift.
Example.swift: Ejemplo conceptual
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: Resultado de la ejecución
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
Ejemplo del mundo real
Example.swift: Ejemplo del mundo real
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: Resultado de la ejecución
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")