스위프트로 작성된 상태
상태는 객체의 내부 상태가 변경될 때 해당 객체가 행동을 변경할 수 있도록 하는 행동 디자인 패턴입니다.
패턴은 상태 관련 행동들을 별도의 상태 클래스들로 추출하며 또 원래 객체가 자체적으로 작동하는 대신 위에 언급된 클래스들에 작업을 위임하도록 강제합니다.
복잡도:
인기도:
사용 사례들: 상태 패턴은 일반적으로 스위프터 언어에서 대규모 switch 기반 상태 머신들을 객체들로 변환하는 데 사용됩니다.
식별: 객체들의 상태에 따라 행동을 변경하는 메서드들이 있으면 패턴은 상태 패턴으로 초기 식별될 수 있으며 이 상태가 상태 객체들 자체를 포함하여 다른 객체들에 의해 제어되거나 대체될 수 있으면 해당 패턴은 상태 패턴입니다.
개념적인 예시
이 예시는 상태 디자인 패턴의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
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: 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() {} } /// 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...