Menu
Coddy logo textTech

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
Shipped

Das 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.

challenge icon

Aufgabe

Einfach

Lassen 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 ein DocumentState-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, und getStatus(): 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 — Wenn publish() aufgerufen wird, wechselt es das Dokument in den ReviewState und gibt "Document sent for review" zurück. Sein Status ist "Draft".
    • ReviewState — Wenn publish() aufgerufen wird, wechselt es das Dokument in den PublishedState und gibt "Document published" zurück. Sein Status ist "Under Review".
    • PublishedState — Wenn publish() 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 eine Document-Klasse, die als Kontext fungiert. Ihr Dokument sollte:
    • Einen Titel in seinem Konstruktor akzeptieren und im DraftState beginnen
    • Eine setState(DocumentState $state): void-Methode haben, um den aktuellen Zustand zu ändern
    • Eine publish(): string-Methode haben, die an die publish()-Methode des aktuellen Zustands delegiert
    • Eine getStatus(): string-Methode haben, die den Status des aktuellen Zustands zurückgibt
  • main.php — Binden Sie die Datei Document ein. Sie erhalten eine Eingabe: die Anzahl der Male, die publish() für das Dokument aufgerufen werden soll.

    Erstellen Sie ein Document mit dem Titel "My Article". Geben Sie zuerst den initialen Status aus. Rufen Sie dann publish() 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();          // Shipped

Das 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

?>
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung