Menu
Coddy logo textTech

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
Shipped

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

challenge icon

Aufgabe

Einfach

Erstellen 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 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: Binde das DocumentState-Interface ein und erstelle drei Zustandsklassen, die es implementieren:
    • DraftState: Wenn publish() aufgerufen wird, wechselt das Dokument zu ReviewState und gibt "Document sent for review" zurück. Sein Status ist "Draft".
    • ReviewState: Wenn publish() aufgerufen wird, wechselt das Dokument zu 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: Binde die States-Datei ein und erstelle eine Document-Klasse, die als Kontext dient. Dein Dokument sollte:
    • einen Titel in seinem Konstruktor akzeptieren und im DraftState starten
    • eine setState(DocumentState $state): void-Methode zum Ändern des aktuellen Zustands haben
    • 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: Binde die Document-Datei ein. Du erhältst eine Eingabe: die Anzahl der Aufrufe von publish() für das Dokument.

    Erstelle ein Document mit dem Titel "My Article". Gib zuerst den initialen Status aus. Rufe dann publish() 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]"

?>
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

Übe selbstständig: Online-PHP-Compiler