Menu
Coddy logo textTech

Wzorzec stanu

Część sekcji Programowanie obiektowe ścieżki PHP w Coddy. Lekcja 79 z 91.

Wzorzec Stan to behawioralny wzorzec projektowy, który pozwala obiektowi zmieniać swoje zachowanie, gdy zmienia się jego stan wewnętrzny. Zamiast używać złożonych instrukcji warunkowych do obsługi różnych stanów, zamykasz każdy stan w osobnej klasie i delegujesz zachowanie do obiektu reprezentującego bieżący stan.

Pomyśl o automacie sprzedającym. Zachowuje się inaczej w zależności od tego, czy czeka na pieniądze, otrzymał płatność, czy wydaje produkt.

Każdy stan ma własne zasady określające, jakie działania są dozwolone. Wzorzec Stanu modeluje to, tworząc osobną klasę dla każdego stanu.

<?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";
    }
}

Klasa kontekstu przechowuje odwołanie do bieżącego stanu i przekazuje mu wywołania:

<?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();

Wynik:

Pending
Order moved to processing
Processing
Order has been shipped
Shipped

Wzorzec Stan eliminuje rozbudowane instrukcje switch i ułatwia dodawanie nowych stanów. Każda klasa stanu odpowiada za znajomość kolejnego stanu, dzięki czemu logika jest uporządkowana i łatwa w utrzymaniu.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system obiegu dokumentów z użyciem wzorca State. Dokumenty w systemie zarządzania treścią przechodzą przez różne etapy: wersję roboczą, weryfikację i publikację. Każdy etap ma własne zasady dotyczące dozwolonych działań i tego, co dzieje się, gdy próbujesz przejść do kolejnego etapu.

Rozmieścisz swój kod w czterech plikach:

  • DocumentState.php: Zdefiniuj interfejs DocumentState, który określa kontrakt dla wszystkich stanów dokumentu. Powinien mieć dwie metody: publish(Document $document): string do próby przejścia dokumentu do kolejnego etapu oraz getStatus(): string do zwracania nazwy bieżącego stanu.
  • States.php: Dołącz interfejs DocumentState i utwórz trzy klasy stanów, które go implementują:
    • DraftState: Gdy wywołana zostanie metoda publish(), zmienia stan dokumentu na ReviewState i zwraca "Document sent for review". Jego status to "Draft".
    • ReviewState: Gdy wywołana zostanie metoda publish(), zmienia stan dokumentu na PublishedState i zwraca "Document published". Jego status to "Under Review".
    • PublishedState: Gdy wywołana zostanie metoda publish(), zwraca "Document is already published" bez zmiany stanu. Jego status to "Published".
  • Document.php: Dołącz plik States i utwórz klasę Document, która pełni rolę kontekstu. Twój dokument powinien:
    • Przyjmować tytuł w konstruktorze i rozpoczynać w stanie DraftState
    • Mieć metodę setState(DocumentState $state): void do zmiany bieżącego stanu
    • Mieć metodę publish(): string, która deleguje wywołanie do metody publish() bieżącego stanu
    • Mieć metodę getStatus(): string, która zwraca status bieżącego stanu
  • main.php: Dołącz plik Document. Otrzymasz jedno wejście: liczbę wywołań metody publish() dla dokumentu.

    Utwórz obiekt Document z tytułem "My Article". Najpierw wypisz początkowy status. Następnie wywołaj metodę publish() podaną liczbę razy, wypisując wynik każdego wywołania w nowej linii. Na koniec wypisz końcowy status.

    Format wyjścia:

    Status: [initial status]
    [publish result 1]
    [publish result 2]
    ...
    Status: [final status]

Każda klasa stanu wie, który stan następuje po niej w obiegu, dzięki czemu logika przejść jest uporządkowana i łatwa do modyfikacji. Dodanie nowego stanu, takiego jak "Archived", będzie po prostu polegało na utworzeniu nowej klasy i zaktualizowaniu odpowiednich przejść.

Spróbuj swoich sił

<?php

require_once 'Document.php';

// Wczytaj dane wejściowe — liczbę wywołań publish()
$n = intval(trim(fgets(STDIN)));

// TODO: Utwórz Document o tytule "My Article"

// TODO: Wypisz początkowy status w formacie: "Status: [status]"

// TODO: Wywołaj publish() n razy, wypisując każdy wynik w nowym wierszu

// TODO: Wypisz końcowy status w formacie: "Status: [status]"

?>
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator PHP online