Wzorzec stanu
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 100 z 104.
Wzorzec stanu pozwala obiektowi zmieniać zachowanie, gdy zmienia się jego stan wewnętrzny, sprawiając wrażenie, jakby obiekt zmienił swoją klasę. Zamiast używać złożonych instrukcji warunkowych do obsługi różnych stanów, enkapsulujesz każdy stan w osobnej klasie.
Wzorzec składa się z Kontekstu, który przechowuje odwołanie do bieżącego stanu, interfejsu Stanu, który definiuje zachowanie właściwe dla danego stanu, oraz Stanów konkretnych, które implementują zachowanie dla każdego stanu:
#include <iostream>
#include <memory>
class Document; // Deklaracja wstępna
// Interfejs stanu
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// Kontekst
class Document {
std::unique_ptr<DocumentState> state;
public:
Document();
void setState(std::unique_ptr<DocumentState> newState) {
state = std::move(newState);
}
void publish() { state->publish(*this); }
std::string getStateName() const { return state->getName(); }
};
// Konkretne stany
class Draft : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Draft"; }
};
class Review : public DocumentState {
public:
void publish(Document& doc) override;
std::string getName() const override { return "Review"; }
};
class Published : public DocumentState {
public:
void publish(Document& doc) override {
std::cout << "Already published\n";
}
std::string getName() const override { return "Published"; }
};
void Draft::publish(Document& doc) {
std::cout << "Moving to review\n";
doc.setState(std::make_unique<Review>());
}
void Review::publish(Document& doc) {
std::cout << "Publishing document\n";
doc.setState(std::make_unique<Published>());
}
Document::Document() : state(std::make_unique<Draft>()) {}Każdy stan obsługuje działanie publish() inaczej i odpowiada za przejście do następnego stanu. Document nie musi znać logiki przejść — po prostu deleguje obsługę do bieżącego stanu. Eliminuje to rozbudowane instrukcje switch i ułatwia dodawanie nowych stanów.
Użyj wzorca State, gdy zachowanie obiektu w dużym stopniu zależy od jego stanu i masz wiele instrukcji warunkowych, które przełączają się w zależności od stanu obiektu.
Wyzwanie
ŁatwyZbudujmy sterownik sygnalizacji świetlnej z użyciem wzorca State. Utworzysz system, w którym sygnalizacja świetlna przechodzi przez różne stany (czerwony, żółty, zielony), a każdy stan określa, co dzieje się po zmianie światła i jaki komunikat jest wyświetlany. To klasyczny przykład wzorca State: zachowanie sygnalizacji świetlnej zależy całkowicie od jej bieżącego stanu.
Rozmieścisz kod w trzech plikach:
TrafficLightState.h: Zdefiniuj interfejs stanu i wszystkie konkretne klasy stanów.Utwórz abstrakcyjną klasę
TrafficLightStatezawierającą:- Czysto wirtualną metodę
change(TrafficLight& light), która obsługuje przejście do następnego stanu - Czysto wirtualną metodę
getColor(), która zwraca bieżący kolor jako ciąg znaków - Czysto wirtualną metodę
getAction(), która zwraca informację o tym, co powinni zrobić kierowcy (np. "Stop", "Caution", "Go") - Wirtualny destruktor
Zaimplementuj trzy konkretne stany:
RedState: kolor to"Red", akcja to"Stop", przechodzi w stan zielonyYellowState: kolor to"Yellow", akcja to"Caution", przechodzi w stan czerwonyGreenState: kolor to"Green", akcja to"Go", przechodzi w stan żółty
Potrzebujesz deklaracji wstępnej klasy
TrafficLight, ponieważ odwołują się do niej stany.- Czysto wirtualną metodę
TrafficLight.h: Utwórz klasę kontekstu, która przechowuje bieżący stan.Klasa
TrafficLightpowinna przechowywaćstd::unique_ptr<TrafficLightState>i domyślnie rozpoczynać w stanie czerwonym. Zaimplementuj:setState(std::unique_ptr<TrafficLightState> newState): zmienia bieżący stanchange(): deleguje do metody change bieżącego stanudisplay(): wyświetla stan sygnalizacji w formacie:[Color]: [Action]
main.cpp: Zademonstruj przechodzenie sygnalizacji świetlnej przez kolejne stany.Wczytaj jedną wartość: liczbę zmian stanu do wykonania (liczbę całkowitą).
Utwórz obiekt
TrafficLighti wyświetl jego stan początkowy. Następnie wykonaj podaną liczbę zmian, wyświetlając stan po każdej z nich.
Na przykład dla wejścia 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopDla wejścia 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopZwróć uwagę, jak sygnalizacja świetlna przechodzi przez swoje stany według przewidywalnego schematu: czerwony → zielony → żółty → czerwony. Każdy stan wie, który stan następuje po nim, i sam obsługuje własne przejście. Klasa TrafficLight nie potrzebuje żadnej logiki warunkowej, aby określić, co się stanie. Po prostu deleguje działanie do stanu, w którym aktualnie się znajduje.
Spróbuj swoich sił
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Wczytaj liczbę zmian stanu
int numChanges;
cin >> numChanges;
// TODO: Utwórz obiekt TrafficLight
// TODO: Wyświetl stan początkowy
// TODO: Wykonaj podaną liczbę zmian
// Po każdej zmianie wyświetl bieżący stan
return 0;
}
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ętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów11Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMixiny z użyciem CRTPIdiom PimplWymazywanie typówKlasy enum i silne typowanieObsługa wyjątków w OOPWłasne hierarchie wyjątków14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuRAII jako wzorzec3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online