Wzorzec stanu
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 74 z 87.
Wzorzec stanu to behawioralny wzorzec projektowy, który pozwala obiektowi zmieniać swoje zachowanie, gdy zmienia się jego stan wewnętrzny. Obiekt sprawia wrażenie, jakby zmieniał swoją klasę, ale w rzeczywistości deleguje zachowanie do różnych obiektów stanu. Pomyśl o automacie: jego reakcja na naciśnięcie przycisku zależy od tego, czy jest bezczynny, czy włożono do niego pieniądze, czy wydaje produkt.
Zamiast używać złożonych instrukcji warunkowych do obsługi różnych stanów, hermetyzujesz każdy stan w jego własnej klasie. Wzorzec obejmuje kontekst, który przechowuje odwołanie do bieżącego stanu, oraz klasy State, które definiują zachowanie dla każdego stanu:
interface State {
void handle(Context context);
}
class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
state.handle(this);
}
}Każdy konkretny stan implementuje zachowanie i może inicjować przejścia do innych stanów:
class IdleState implements State {
public void handle(Context context) {
System.out.println("Idle: Insert coin to start");
context.setState(new ActiveState());
}
}
class ActiveState implements State {
public void handle(Context context) {
System.out.println("Active: Processing...");
context.setState(new IdleState());
}
}Kontekst przekazuje żądania do swojego bieżącego stanu, a zachowanie zmienia się automatycznie wraz ze zmianą stanu:
Context context = new Context();
context.setState(new IdleState());
context.request(); // Bezczynny: Wrzuć monetę, aby rozpocząć
context.request(); // Aktywny: Przetwarzanie...Wzorzec Stan sprawdza się idealnie, gdy zachowanie obiektu w dużym stopniu zależy od jego stanu i musi zmieniać się w czasie działania programu. Eliminuje rozbudowane bloki warunkowe i ułatwia dodawanie nowych stanów — wystarczy utworzyć nową klasę implementującą interfejs State.
Wyzwanie
ŁatwyZbudujmy system obiegu dokumentów, używając wzorca State! Utworzysz system modelujący, jak dokument przechodzi przez różne etapy: Draft, Review i Published, przy czym każdy stan określa dostępne działania oraz zachowanie dokumentu podczas próby edycji lub zatwierdzenia.
Rozmieścisz kod w czterech plikach:
DocumentState.java: Zdefiniuj interfejsDocumentState, który będą implementować wszystkie stany. Powinien deklarować dwie metody:edit(Document doc)służącą do próby edycji dokumentu orazapprove(Document doc)służącą do próby zatwierdzenia dokumentu lub przejścia do następnego etapu.States.java: Utwórz trzy konkretne klasy stanów implementująceDocumentState:DraftState— po wywołaniueditwypiszEditing draft.... Po wywołaniuapprovewypiszDraft approved. Moving to review.i przełącz dokument w stanReviewState.ReviewState— po wywołaniueditwypiszCannot edit during review.(bez zmiany stanu). Po wywołaniuapprovewypiszReview complete. Publishing document.i przełącz dokument w stanPublishedState.PublishedState— po wywołaniueditwypiszCannot edit published document.. Po wywołaniuapprovewypiszDocument already published.. Żadne z tych działań nie zmienia stanu.Document.java: Utwórz klasę kontekstu, która przechowuje bieżący stan. KlasaDocumentpowinna mieć:Prywatne pole przechowujące bieżący
DocumentState, inicjalizowane w konstruktorze jakoDraftState.Metodę
setState(DocumentState state), która zmienia bieżący stan.Metodę
edit(), która przekazuje wywołanie do metodyeditbieżącego stanu.Metodę
approve(), która przekazuje wywołanie do metodyapprovebieżącego stanu.Metodę
getStatus(), która zwraca prostą nazwę klasy bieżącego stanu (użyjgetClass().getSimpleName()).Main.java: Zademonstruj działanie systemu stanów! Otrzymasz jedno wejście: sekwencję działań w postaci ciągu znaków rozdzielonych przecinkami, gdzie każde działanie toeditlubapprove(na przykład:edit,approve,edit,approve).Utwórz obiekt
Documenti wypisz jego początkowy status w formacieStatus: [state]. Następnie wykonuj po kolei każde działanie, wywołując odpowiednią metodę na dokumencie. Po każdym działaniu wypisz bieżący status.
Otrzymasz jedno wejście: ciąg działań rozdzielonych przecinkami.
Na przykład dla wejścia edit,approve,approve wynikiem będzie:
Status: DraftState
Editing draft...
Status: DraftState
Draft approved. Moving to review.
Status: ReviewState
Review complete. Publishing document.
Status: PublishedStateZauważ, że to samo wywołanie approve() daje różne wyniki w zależności od bieżącego stanu dokumentu. Na tym polega istota wzorca State! Dokument nie używa instrukcji warunkowych do sprawdzania swojego stanu; zamiast tego przekazuje obsługę działania bieżącemu obiektowi stanu, który odpowiednio je obsługuje i w razie potrzeby zmienia stan.
Spróbuj swoich sił
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();
// TODO: Utwórz nowy Document
// TODO: Wypisz początkowy status w formacie "Status: [state]"
// TODO: Podziel dane wejściowe według przecinków, aby uzyskać poszczególne akcje
// TODO: Przejdź przez każdą akcję
// - Jeśli akcja to "edit", wywołaj edit() na document
// - Jeśli akcja to "approve", wywołaj approve() na document
// - Po każdej akcji wypisz bieżący status
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatności9Typy generyczne
Wprowadzenie do typów generycznychKlasy generyczneMetody generyczneOgraniczone parametry typówSymbole wieloznaczne (?, extends, super)Powtórzenie – kontener generyczny12Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuWzorzec iteratoraPoćwicz samodzielnie: Kompilator Java online