Patron d’état
Fait partie de la section Programmation orientée objet du Journey Swift de Coddy. Leçon 49 sur 57.
Le modèle state permet à un objet de changer de comportement lorsque son état interne change. Chaque état est son propre type, qui décide de ce qui se passe et de l’état suivant :
protocol LightState {
var name: String { get }
func next() -> any LightState
}
struct Red: LightState { var name: String { "red" }; func next() -> any LightState { Green() } }
struct Green: LightState { var name: String { "green" }; func next() -> any LightState { Yellow() } }
struct Yellow: LightState { var name: String { "yellow" }; func next() -> any LightState { Red() } }
var light: any LightState = Red()
for _ in 1...4 {
print(light.name)
light = light.next()
}Sortie :
red
green
yellow
redL’objet qui possède l’état transmet les requêtes à son état actuel. Chaque état gère un événement à sa manière, et les actions illégales sont refusées par les états qui ne les autorisent pas :
protocol OrderState {
var name: String { get }
func pay() -> (any OrderState)?
func ship() -> (any OrderState)?
}
struct New: OrderState { var name: String { "new" }; func pay() -> (any OrderState)? { Paid() }; func ship() -> (any OrderState)? { nil } }
struct Paid: OrderState { var name: String { "paid" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { Shipped() } }
struct Shipped: OrderState { var name: String { "shipped" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { nil } }
final class Order {
private(set) var state: any OrderState = New()
func handle(_ event: String) -> String {
guard let next = event == "pay" ? state.pay() : state.ship() else { return "cannot \(event) when \(state.name)" }
state = next
return "now \(state.name)"
}
}
let order = Order()
for e in ["ship", "pay", "pay", "ship"] { print(order.handle(e)) }Sortie :
cannot ship when new
now paid
cannot pay when paid
now shippedPour un petit nombre d’états sans données propres, un enum avec un switch exprime souvent la même idée de manière plus concise, le compilateur vérifiant que chaque état est traité :
enum Door {
case open, closed, locked
func handle(_ action: String) -> Door? {
switch (self, action) {
case (.closed, "open"): return .open
case (.open, "close"): return .closed
case (.closed, "lock"): return .locked
case (.locked, "unlock"): return .closed
default: return nil
}
}
}
var door = Door.closed
for a in ["lock", "open", "unlock", "open"] {
if let next = door.handle(a) { door = next; print("\(a) -> \(door)") } else { print("cannot \(a)") }
}Résultat :
lock -> locked
cannot open
unlock -> closed
open -> openLes types d’état sont utiles lorsque chaque état possède plusieurs comportements ou ses propres données. Dans tous les cas, les règles de chaque état sont regroupées au même endroit au lieu d’avoir des vérifications if dispersées dans l’objet.
// dispersé : chaque méthode vérifie l'état
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// pattern State : chaque état répond pour lui-même
state = state.ship() ?? stateDéfi
FacileModélisez un distributeur automatique avec une énumération d’état Vending comportant les cas idle, hasCoin(Int) et soldOut. handle(_ event:) renvoie l’état suivant ou nil pour un événement qui n’est pas autorisé : coin <n> dans idle passe à hasCoin(n), et dans hasCoin, ajoute au montant ; buy dans hasCoin avec au moins 150 passe à idle (vente réussie) ; refund dans hasCoin passe à idle ; empty dans n’importe quel état passe à soldOut ; refill dans soldOut passe à idle.
Le code fourni applique les événements dans l’ordre et affiche l’état après chacun (idle, coin 100, sold out) ou not allowed: <event>.
Votre code doit être placé dans Vending.swift. main.swift contient le code d’entrée/sortie fourni et ne peut pas être modifié.
Essayez vous-même
// Code d'entrée/sortie fourni : le laisser tel quel
var input: [String] = []
while let line = readLine() { input.append(line) }
var machine = Vending.idle
for event in input {
if let next = machine.handle(event) {
machine = next
print(machine.label)
} else {
print("not allowed: \(event)")
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Classes et objets
Travailler avec des fichiersDéfinir des classesMéthodes et selfInitialiseursLes classes sont des référencesRécapitulatif - Carte de bibliothèque4Encapsulation
Contrôle d’accèsLecture seule depuis l’extérieurProtection de l’étatInitialiseurs pouvant échouerRécapitulatif - Compte bancaire7Polymorphisme et génériques
PolymorphismeConversion de typeFonctions génériquesTypes génériquesRécapitulatif - Calculateur de formes2Structures et types valeur
StructuresSémantique de valeurMéthodes mutantesStructure ou classeRécapitulatif - article d’inventaire5Héritage
Sous-classesRedéfinition des méthodesInitialiseurs et superfinal et hiérarchiesRécapitulatif - hiérarchie des employés8Protocoles standard
CustomStringConvertibleEquatableComparableHashableRécapitulatif - Tri personnalisé11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Swift