L’État est un patron de conception comportemental qui permet à un objet de modifier son comportement lorsque son état interne change.
Ce patron extrait les comportements liés aux états dans des classes séparées et force l’objet original à déléguer les tâches à une instance de ces classes, au lieu de le faire lui-même.
Exemple conceptuel
Dans cet exemple, nous allons voir la structure de l’État et répondre aux questions suivantes :
Que contiennent les classes ?
Quels rôles jouent-elles ?
Comment les éléments du patron sont-ils reliés ?
Après avoir étudié la structure du patron, vous pourrez plus facilement comprendre l’exemple suivant qui est basé sur un cas d’utilisation réel en Swift.
Example.swift: Exemple conceptuel
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: Résultat de l’exécution
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
Analogie du monde réel
Example.swift: Analogie du monde réel
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: Résultat de l’exécution
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...