Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy — Lektion 79 von 91.
Das State Pattern ist ein Verhaltensmuster, das es einem Objekt ermöglicht, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Anstatt komplexe Bedingungen zu verwenden, um verschiedene Zustände zu handhaben, kapseln Sie jeden Zustand in einer eigenen Klasse und delegieren das Verhalten an das aktuelle Zustandsobjekt.
Denken Sie an einen Verkaufsautomaten. Er verhält sich unterschiedlich, je nachdem, ob er auf Geld wartet, eine Zahlung erhalten hat oder ein Produkt ausgibt.
Jeder Zustand hat seine eigenen Regeln dafür, welche Aktionen erlaubt sind. Das State-Pattern modelliert dies, indem es für jeden Zustand eine Klasse erstellt.
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}
Die Kontext-Klasse hält eine Referenz auf den aktuellen Zustand und delegiert Aufrufe an diesen:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}
$order = new Order();
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus() . "\n";
echo $order->proceed() . "\n";
echo $order->getStatus();
Ausgabe:
Pending
Order moved to processing
Processing
Order has been shipped
ShippedDas State Pattern eliminiert ausufernde switch-Anweisungen und macht das Hinzufügen neuer Zustände unkompliziert. Jede Zustandsklasse ist dafür verantwortlich zu wissen, welcher Zustand als Nächstes folgt, wodurch die Logik organisiert und wartbar bleibt.
Aufgabe
EinfachLassen Sie uns ein Dokumenten-Workflow-System mit dem State Pattern erstellen. Dokumente in einem Content-Management-System durchlaufen verschiedene Phasen — Entwurf, Prüfung und Veröffentlichung — wobei jede Phase ihre eigenen Regeln dafür hat, welche Aktionen erlaubt sind und was passiert, wenn man versucht, das Dokument weiterzuleiten.
Sie werden Ihren Code über vier Dateien organisieren:
DocumentState.php— Definieren Sie einDocumentState-Interface, das den Vertrag für alle Dokumentzustände festlegt. Es sollte zwei Methoden haben:publish(Document $document): string, um zu versuchen, das Dokument weiterzuleiten, undgetStatus(): string, um den Namen des aktuellen Zustands zurückzugeben.States.php— Binden Sie das DocumentState-Interface ein und erstellen Sie drei Zustandsklassen, die es implementieren:DraftState— Wennpublish()aufgerufen wird, wechselt es das Dokument in denReviewStateund gibt"Document sent for review"zurück. Sein Status ist"Draft".ReviewState— Wennpublish()aufgerufen wird, wechselt es das Dokument in denPublishedStateund gibt"Document published"zurück. Sein Status ist"Under Review".PublishedState— Wennpublish()aufgerufen wird, gibt es"Document is already published"zurück, ohne den Zustand zu ändern. Sein Status ist"Published".
Document.php— Binden Sie die Datei States ein und erstellen Sie eineDocument-Klasse, die als Kontext fungiert. Ihr Dokument sollte:- Einen Titel in seinem Konstruktor akzeptieren und im
DraftStatebeginnen - Eine
setState(DocumentState $state): void-Methode haben, um den aktuellen Zustand zu ändern - Eine
publish(): string-Methode haben, die an diepublish()-Methode des aktuellen Zustands delegiert - Eine
getStatus(): string-Methode haben, die den Status des aktuellen Zustands zurückgibt
- Einen Titel in seinem Konstruktor akzeptieren und im
main.php— Binden Sie die Datei Document ein. Sie erhalten eine Eingabe: die Anzahl der Male, diepublish()für das Dokument aufgerufen werden soll.Erstellen Sie ein
Documentmit dem Titel"My Article". Geben Sie zuerst den initialen Status aus. Rufen Sie dannpublish()so oft wie angegeben auf und geben Sie das Ergebnis jedes Aufrufs in einer neuen Zeile aus. Geben Sie schließlich den endgültigen Status aus.Ausgabeformat:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Jede Zustandsklasse weiß, welcher Zustand als Nächstes im Workflow folgt, wodurch die Übergangslogik organisiert und leicht zu ändern bleibt. Das Hinzufügen eines neuen Zustands wie "Archived" würde einfach bedeuten, eine neue Klasse zu erstellen und die relevanten Übergänge zu aktualisieren.
Spickzettel
Das State Pattern (Zustandsmuster) ist ein Verhaltensmuster, das es einem Objekt ermöglicht, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert, indem jeder Zustand in einer eigenen Klasse gekapselt wird.
Definieren Sie ein Zustands-Interface, das alle Zustandsklassen implementieren werden:
<?php
interface OrderState {
public function proceed(Order $order): string;
public function getStatus(): string;
}Erstellen Sie konkrete Zustandsklassen, die das Interface implementieren:
<?php
class PendingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ProcessingState());
return "Order moved to processing";
}
public function getStatus(): string {
return "Pending";
}
}
class ProcessingState implements OrderState {
public function proceed(Order $order): string {
$order->setState(new ShippedState());
return "Order has been shipped";
}
public function getStatus(): string {
return "Processing";
}
}
class ShippedState implements OrderState {
public function proceed(Order $order): string {
return "Order already shipped";
}
public function getStatus(): string {
return "Shipped";
}
}Erstellen Sie eine Kontextklasse, die eine Referenz auf den aktuellen Zustand hält und Aufrufe an diesen delegiert:
<?php
class Order {
private OrderState $state;
public function __construct() {
$this->state = new PendingState();
}
public function setState(OrderState $state): void {
$this->state = $state;
}
public function proceed(): string {
return $this->state->proceed($this);
}
public function getStatus(): string {
return $this->state->getStatus();
}
}Anwendungsbeispiel:
$order = new Order();
echo $order->getStatus() . "\n"; // Pending
echo $order->proceed() . "\n"; // Order moved to processing
echo $order->getStatus() . "\n"; // Processing
echo $order->proceed() . "\n"; // Order has been shipped
echo $order->getStatus(); // ShippedDas State Pattern eliminiert komplexe Switch-Anweisungen und macht das Hinzufügen neuer Zustände unkompliziert. Jede Zustandsklasse ist dafür verantwortlich, zu wissen, welcher Zustand als Nächstes folgt.
Probier es selbst
<?php
require_once 'Document.php';
// Eingabe lesen - Anzahl der Aufrufe von publish()
$n = intval(trim(fgets(STDIN)));
// TODO: Erstelle ein Dokument mit dem Titel "My Article"
// TODO: Gib den Anfangsstatus im Format "Status: [status]" aus
// TODO: Rufe publish() n-mal auf und gib jedes Ergebnis in einer neuen Zeile aus
// TODO: Gib den Endstatus im Format "Status: [status]" aus
?>Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienEinführung in die OOPKlassen vs. ObjekteDas $this-SchlüsselwortMethodenEigenschaftenKonstruktor (__construct)Destruktor (__destruct)Rückblick - Einfacher Taschenrechner4Vererbung
Grundlagen der VererbungDas parent:: SchlüsselwortMethoden überschreibenDas final SchlüsselwortAbstrakte KlassenZusammenfassung – Mitarbeiter-Hierarchie7Kapselung
Public, Protected, PrivateZugriffsmodifikatoren im DetailGetter und SetterInformation HidingConstructor Promotion (8.0)Zusammenfassung – Studentenverwaltungssystem10Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungDependency InjectionAnonyme KlassenEnums (PHP 8.1)Fibers (PHP 8.1)Objekt-Klonen im DetailGeneratoren & Iteratoren13Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterZustandsmusterComposite-MusterRepository-Muster2Namensräume & Autoloading
Einführung in NamensräumeDas use-SchlüsselwortPSR-4 Autoloading-StandardComposer AutoloaderZusammenfassung – Organisiertes Projekt5Interfaces & Verträge
Einführung in InterfacesInterfaces implementierenMehrfache Interface-ImplementierungInterface vs. Abstrakte KlasseType Hinting mit InterfacesZusammenfassung – Formen-Rechner8Magische Methoden
Einführung in magische Methoden__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Objekt-Klonen__serialize & __unserializeZusammenfassung - Eigene Collection11Typsystem & Fehlerbehandlung
TypdeklarationenNullable-TypenUnion- & Intersection-TypenException-KlassenEigene Exception-HierarchieTry, Catch, FinallyZusammenfassung - Formular-Validator14Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen3Klasseneigenschaften
Instanz- vs. statische EigenschaftenKonstanten in KlassenStatische Methoden & EigenschaftenPrivate & geschützte EigenschaftenReadonly-Eigenschaften (PHP 8.1)Zusammenfassung – Bankkonto-Manager6Polymorphismus
Method Overriding erneut betrachtetPolymorphismus über InterfacesType Hinting & Union TypesLate Static BindingZusammenfassung – Payment Processor9Traits
Einführung in TraitsVerwendung mehrerer TraitsKonfliktlösung bei TraitsAbstrakte Methoden in TraitsTraits vs. Vererbung12Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster15Abschlussherausforderungen
E-Learning-PlattformBanksystemSpielcharakter-EntwicklungFahrzeugvermietung