Состояние — это поведенческий паттерн, позволяющий динамически изменять поведение объекта при смене его состояния.
Поведения, зависящие от состояния, переезжают в отдельные классы. Первоначальный класс хранит ссылку на один из таких объектов-состояний и делегирует ему работу.
Концептуальный пример
Этот пример показывает структуру паттерна Состояние , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире 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: class {
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...