
Swift 状态模式讲解和代码示例
状态是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为。
该模式将与状态相关的行为抽取到独立的状态类中, 让原对象将工作委派给这些类的实例, 而不是自行进行处理。
复杂度:
流行度:
使用示例: 在 Swift 语言中, 状态模式通常被用于将基于 switch
语句的大型状态机转换为对象。
识别方法: 状态模式可通过受外部控制且能根据对象状态改变行为的方法来识别。
以下示例可在
Swift Playgrounds
上使用。
感谢
Alejandro Mohamad 创建了Playground版本。
概念示例
本例说明了状态设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 Swift 应用案例。
Example.swift: 概念示例
import XCTest
/// The Context defines the interface of interest to clients. It also maintains
/// a reference to an instance of a State subclass, which represents the current
/// state of the Context.
class Context {
/// A reference to the current state of the Context.
private var state: State
init(_ state: State) {
self.state = state
transitionTo(state: state)
}
/// The Context allows changing the State object at runtime.
func transitionTo(state: State) {
print("Context: Transition to " + String(describing: state))
self.state = state
self.state.update(context: self)
}
/// The Context delegates part of its behavior to the current State object.
func request1() {
state.handle1()
}
func request2() {
state.handle2()
}
}
/// The base State class declares methods that all Concrete State should
/// implement and also provides a backreference to the Context object,
/// associated with the State. This backreference can be used by States to
/// transition the Context to another State.
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() {}
}
/// Concrete States implement various behaviors, associated with a state of the
/// Context.
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())
}
}
/// Let's see how it all works together.
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...