Состояние на Swift
Состояние — это поведенческий паттерн, позволяющий динамически изменять поведение объекта при смене его состояния.
Поведения, зависящие от состояния, переезжают в отдельные классы. Первоначальный класс хранит ссылку на один из таких объектов-состояний и делегирует ему работу.
Сложность:
Популярность:
Применимость: Паттерн Состояние часто используют в Swift для превращения в объекты громоздких стейт-машин, построенных на операторах switch.
Признаки применения паттерна: Методы класса делегируют работу одному вложенному объекту.
Концептуальный пример
Этот пример показывает структуру паттерна Состояние, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире Swift.
Example.swift: Пример структуры паттерна
import XCTest /// Контекст определяет интерфейс, представляющий интерес для клиентов. Он также /// хранит ссылку на экземпляр подкласса Состояния, который отображает текущее /// состояние Контекста. class Context { /// Ссылка на текущее состояние Контекста. private var state: State init(_ state: State) { self.state = state transitionTo(state: state) } /// Контекст позволяет изменять объект Состояния во время выполнения. func transitionTo(state: State) { print("Context: Transition to " + String(describing: state)) self.state = state self.state.update(context: self) } /// Контекст делегирует часть своего поведения текущему объекту Состояния. func request1() { state.handle1() } func request2() { state.handle2() } } /// Базовый класс Состояния объявляет методы, которые должны реализовать все /// Конкретные Состояния, а также предоставляет обратную ссылку на объект /// Контекст, связанный с Состоянием. Эта обратная ссылка может использоваться /// Состояниями для передачи Контекста другому Состоянию. protocol State: AnyObject { func update(context: Context) func handle1() func handle2() } class BaseState: State { private(set) weak var context: Context? func update(context: Context) { self.context = context } func handle1() {} func handle2() {} } /// Конкретные Состояния реализуют различные модели поведения, связанные с /// состоянием Контекста. class ConcreteStateA: BaseState { override func handle1() { print("ConcreteStateA handles request1.") print("ConcreteStateA wants to change the state of the context.\n") context?.transitionTo(state: ConcreteStateB()) } override func handle2() { print("ConcreteStateA handles request2.\n") } } class ConcreteStateB: BaseState { override func handle1() { print("ConcreteStateB handles request1.\n") } override func handle2() { print("ConcreteStateB handles request2.") print("ConcreteStateB wants to change the state of the context.\n") context?.transitionTo(state: ConcreteStateA()) } } /// Давайте посмотрим как всё это будет работать. class StateConceptual: XCTestCase { func test() { let context = Context(ConcreteStateA()) context.request1() context.request2() } } Output.txt: Результат выполнения
Context: Transition to StateConceptual.ConcreteStateA ConcreteStateA handles request1. ConcreteStateA wants to change the state of the context. Context: Transition to StateConceptual.ConcreteStateB ConcreteStateB handles request2. ConcreteStateB wants to change the state of the context. Context: Transition to StateConceptual.ConcreteStateA Пример из реальной жизни
Example.swift: Пример из реальной жизни
import XCTest class StateRealWorld: XCTestCase { func test() { print("Client: I'm starting working with a location tracker") let tracker = LocationTracker() print() tracker.startTracking() print() tracker.pauseTracking(for: 2) print() tracker.makeCheckIn() print() tracker.findMyChildren() print() tracker.stopTracking() } } class LocationTracker { /// Location tracking is enabled by default private lazy var trackingState: TrackingState = EnabledTrackingState(tracker: self) func startTracking() { trackingState.startTracking() } func stopTracking() { trackingState.stopTracking() } func pauseTracking(for time: TimeInterval) { trackingState.pauseTracking(for: time) } func makeCheckIn() { trackingState.makeCheckIn() } func findMyChildren() { trackingState.findMyChildren() } func update(state: TrackingState) { trackingState = state } } protocol TrackingState { func startTracking() func stopTracking() func pauseTracking(for time: TimeInterval) func makeCheckIn() func findMyChildren() } class EnabledTrackingState: TrackingState { private weak var tracker: LocationTracker? init(tracker: LocationTracker?) { self.tracker = tracker } func startTracking() { print("EnabledTrackingState: startTracking is invoked") print("EnabledTrackingState: tracking location....1") print("EnabledTrackingState: tracking location....2") print("EnabledTrackingState: tracking location....3") } func stopTracking() { print("EnabledTrackingState: Received 'stop tracking'") print("EnabledTrackingState: Changing state to 'disabled'...") tracker?.update(state: DisabledTrackingState(tracker: tracker)) tracker?.stopTracking() } func pauseTracking(for time: TimeInterval) { print("EnabledTrackingState: Received 'pause tracking' for \(time) seconds") print("EnabledTrackingState: Changing state to 'disabled'...") tracker?.update(state: DisabledTrackingState(tracker: tracker)) tracker?.pauseTracking(for: time) } func makeCheckIn() { print("EnabledTrackingState: performing check-in at the current location") } func findMyChildren() { print("EnabledTrackingState: searching for children...") } } class DisabledTrackingState: TrackingState { private weak var tracker: LocationTracker? init(tracker: LocationTracker?) { self.tracker = tracker } func startTracking() { print("DisabledTrackingState: Received 'start tracking'") print("DisabledTrackingState: Changing state to 'enabled'...") tracker?.update(state: EnabledTrackingState(tracker: tracker)) } func pauseTracking(for time: TimeInterval) { print("DisabledTrackingState: Pause tracking for \(time) seconds") for i in 0...Int(time) { print("DisabledTrackingState: pause...\(i)") } print("DisabledTrackingState: Time is over") print("DisabledTrackingState: Returing to 'enabled state'...\n") self.tracker?.update(state: EnabledTrackingState(tracker: self.tracker)) self.tracker?.startTracking() } func stopTracking() { print("DisabledTrackingState: Received 'stop tracking'") print("DisabledTrackingState: Do nothing...") } func makeCheckIn() { print("DisabledTrackingState: Received 'make check-in'") print("DisabledTrackingState: Changing state to 'enabled'...") tracker?.update(state: EnabledTrackingState(tracker: tracker)) tracker?.makeCheckIn() } func findMyChildren() { print("DisabledTrackingState: Received 'find my children'") print("DisabledTrackingState: Changing state to 'enabled'...") tracker?.update(state: EnabledTrackingState(tracker: tracker)) tracker?.findMyChildren() } } Output.txt: Результат выполнения
Client: I'm starting working with a location tracker EnabledTrackingState: startTracking is invoked EnabledTrackingState: tracking location....1 EnabledTrackingState: tracking location....2 EnabledTrackingState: tracking location....3 EnabledTrackingState: Received 'pause tracking' for 2.0 seconds EnabledTrackingState: Changing state to 'disabled'... DisabledTrackingState: Pause tracking for 2.0 seconds DisabledTrackingState: pause...0 DisabledTrackingState: pause...1 DisabledTrackingState: pause...2 DisabledTrackingState: Time is over DisabledTrackingState: Returing to 'enabled state'... EnabledTrackingState: startTracking is invoked EnabledTrackingState: tracking location....1 EnabledTrackingState: tracking location....2 EnabledTrackingState: tracking location....3 EnabledTrackingState: performing check-in at the current location EnabledTrackingState: searching for children... EnabledTrackingState: Received 'stop tracking' EnabledTrackingState: Changing state to 'disabled'... DisabledTrackingState: Received 'stop tracking' DisabledTrackingState: Do nothing...