Zustandsmuster
Teil des Abschnitts Objektorientierte Programmierung der PHP-Journey von Coddy. Lektion 79 von 91.
Das State Pattern ist ein Verhaltensentwurfsmuster, das einem Objekt ermöglicht, sein Verhalten zu ändern, wenn sich sein interner Zustand ändert. Anstatt komplexe Bedingungen zur Behandlung verschiedener Zustände zu verwenden, kapselst du jeden Zustand in seiner eigenen Klasse und delegierst das Verhalten an das aktuelle Zustandsobjekt.
Stell dir einen Verkaufsautomaten vor. 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 zulässig sind. Das State Pattern bildet dies ab, 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 Kontextklasse enthält eine Referenz auf den aktuellen Zustand und delegiert Aufrufe an ihn:
<?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 Zustandsmuster beseitigt 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 übersichtlich und wartbar bleibt.
Aufgabe
EinfachErstellen wir ein Dokument-Workflow-System mithilfe des Zustandsmusters. Dokumente in einem Content-Management-System durchlaufen verschiedene Phasen: Entwurf, Überprüfung und Veröffentlichung. Jede Phase hat eigene Regeln dafür, welche Aktionen zulässig sind und was geschieht, wenn du versuchst, das Dokument weiterzuleiten.
Du organisierst deinen Code auf vier Dateien verteilt:
DocumentState.php: Definiere 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: Binde das DocumentState-Interface ein und erstelle drei Zustandsklassen, die es implementieren:DraftState: Wennpublish()aufgerufen wird, wechselt das Dokument zuReviewStateund gibt"Document sent for review"zurück. Sein Status ist"Draft".ReviewState: Wennpublish()aufgerufen wird, wechselt das Dokument zuPublishedStateund 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: Binde die States-Datei ein und erstelle eineDocument-Klasse, die als Kontext dient. Dein Dokument sollte:- einen Titel in seinem Konstruktor akzeptieren und im
DraftStatestarten - eine
setState(DocumentState $state): void-Methode zum Ändern des aktuellen Zustands haben - 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: Binde die Document-Datei ein. Du erhältst eine Eingabe: die Anzahl der Aufrufe vonpublish()für das Dokument.Erstelle ein
Documentmit dem Titel"My Article". Gib zuerst den initialen Status aus. Rufe dannpublish()so oft wie angegeben auf und gib das Ergebnis jedes Aufrufs in einer neuen Zeile aus. Gib schließlich den finalen Status aus.Ausgabeformat:
Status: [initial status] [publish result 1] [publish result 2] ... Status: [final status]
Jede Zustandsklasse weiß, welcher Zustand im Workflow als Nächstes folgt, wodurch die Übergangslogik organisiert und leicht anpassbar 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.
Probier es selbst
<?php
require_once 'Document.php';
// Eingabe lesen - Anzahl der Aufrufe von publish()
$n = intval(trim(fgets(STDIN)));
// TODO: Erstelle ein Document mit dem Titel "My Article"
// TODO: Gib den initialen Status im Format aus: "Status: [status]"
// TODO: Rufe publish() n-mal auf und gib jedes Ergebnis in einer neuen Zeile aus
// TODO: Gib den finalen Status im Format aus: "Status: [status]"
?>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 OOPKlassen vs. ObjekteDas Schlüsselwort $thisMethodenEigenschaftenKonstruktor (__construct)Destruktor (__destruct)Rückblick – Einfacher Taschenrechner4Vererbung
Grundlegende VererbungDas Schlüsselwort parent::MethodenüberschreibungDas Schlüsselwort finalAbstrakte KlassenRückblick – Mitarbeiterhierarchie7Kapselung
Öffentlich, geschützt, privatZugriffsmodifikatoren im DetailGetter und SetterInformationsverbergungKonstruktor-Promotion (8.0)Wiederholung – System zur Verwaltung von Schülerdatensätzen10Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungDependency InjectionAnonyme KlassenEnums (PHP 8.1)Fibers (PHP 8.1)Objektklonen im DetailGeneratoren & Iteratoren13Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterSchablonenmethodenmusterZustandsmusterKompositummusterRepository-Muster2Namespaces & Autoloading
Einführung in NamespacesDas use-SchlüsselwortPSR-4-Autoloading-StandardComposer-AutoloaderRückblick – Organisiertes Projekt5Schnittstellen & Verträge
Einführung in SchnittstellenSchnittstellen implementierenMehrere Schnittstellen implementierenSchnittstelle vs. abstrakte KlasseTyp-Hinweise mit SchnittstellenRückblick – Formrechner8Magische Methoden
Einführung in magische Methoden__toString & __debugInfo__get, __set, __isset, __unset__call & __callStatic__clone & Objektklonen__serialize & __unserializeRückblick – Benutzerdefinierte Sammlung11Typsystem & Fehlerbehandlung
TypdeklarationenNullable-TypenUnion- & Intersection-TypenException-KlassenBenutzerdefinierte Exception-HierarchieTry, Catch, FinallyRückblick – Formularvalidator14Projekt: Bibliotheksverwaltung
ProjektübersichtBuch- und Benutzerklassen3Klassen-Eigenschaften
Instanz- vs. statische EigenschaftenKonstanten in KlassenStatische Methoden & EigenschaftenPrivate & geschützte EigenschaftenSchreibgeschützte Eigenschaften (PHP 8.1)Zusammenfassung – Bankkonto-Manager6Polymorphie
Methodenüberschreibung erneut betrachtetPolymorphie über SchnittstellenTyp-Hinweise & Union-TypenSpätes statisches BindenRückblick – Zahlungsabwickler9Traits
Einführung in TraitsMehrere Traits verwendenAuflösung von Trait-KonfliktenAbstrakte Methoden in TraitsTraits vs. Vererbung12Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterObserver-MusterStrategy-Muster15Abschließende Herausforderungen
E-Learning-PlattformBankingsystemEntwicklung einer SpielfigurFahrzeugvermietungÜbe selbstständig: Online-PHP-Compiler