Паттерн Состояние
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 100 из 104.
Паттерн State (Состояние) позволяет объекту изменять свое поведение при изменении его внутреннего состояния, благодаря чему создается впечатление, что объект изменил свой класс. Вместо использования сложных условных операторов для обработки различных состояний, каждое состояние инкапсулируется в отдельный класс.
Паттерн состоит из Контекста, который хранит ссылку на текущее состояние, интерфейса Состояния, определяющего поведение, специфичное для состояния, и Конкретных состояний, которые реализуют поведение для каждого состояния:
#include <iostream>
#include <memory>
class Document; // Предварительное объявление
// Интерфейс состояния
class DocumentState {
public:
virtual void publish(Document& doc) = 0;
virtual std::string getName() const = 0;
virtual ~DocumentState() = default;
};
// Контекст
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(); }
};
// Конкретные состояния
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>()) {}Каждое состояние обрабатывает действие publish() по-разному и отвечает за переход к следующему состоянию. Document не нужно знать логику переходов — он просто делегирует выполнение текущему состоянию. Это избавляет от громоздких операторов switch и делает добавление новых состояний простым процессом.
Используйте Состояние (State), когда поведение объекта сильно зависит от его состояния и у вас есть много условных операторов, переключающихся в зависимости от состояния объекта.
Задание
ЛегкоДавайте создадим Контроллер светофора, используя паттерн Состояние (State). Вы создадите систему, в которой светофор циклически переключается между различными состояниями (Red, Yellow, Green), при этом каждое состояние определяет, что происходит при смене света и какое сообщение выводится на экран. Это классический пример паттерна Состояние — поведение светофора полностью зависит от его текущего состояния.
Вы организуете свой код в трех файлах:
TrafficLightState.h: Определите интерфейс состояния и все конкретные классы состояний.Создайте абстрактный класс
TrafficLightStateс:- Чисто виртуальным методом
change(TrafficLight& light), который обрабатывает переход к следующему состоянию - Чисто виртуальным методом
getColor(), который возвращает текущий цвет в виде строки - Чисто виртуальным методом
getAction(), который возвращает то, что должны делать водители (например, "Stop", "Caution", "Go") - Виртуальным деструктором
Реализуйте три конкретных состояния:
RedState— цвет"Red", действие"Stop", меняется на GreenYellowState— цвет"Yellow", действие"Caution", меняется на RedGreenState— цвет"Green", действие"Go", меняется на Yellow
Вам понадобится опережающее объявление (forward declaration) для
TrafficLight, так как состояния ссылаются на него.- Чисто виртуальным методом
TrafficLight.h: Создайте класс контекста, который хранит текущее состояние.Ваш класс
TrafficLightдолжен содержатьstd::unique_ptr<TrafficLightState>и по умолчанию начинаться с состояния Red. Реализуйте:setState(std::unique_ptr<TrafficLightState> newState)— изменяет текущее состояниеchange()— делегирует выполнение методу change текущего состоянияdisplay()— выводит статус светофора в формате:[Color]: [Action]
main.cpp: Продемонстрируйте циклическое переключение светофора через состояния.Считайте одно входное значение: количество изменений состояния, которые необходимо выполнить (целое число).
Создайте объект
TrafficLightи отобразите его начальное состояние. Затем выполните указанное количество изменений, отображая состояние после каждого изменения.
Например, при входных данных 3:
Red: Stop
Green: Go
Yellow: Caution
Red: StopПри входных данных 6:
Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: StopОбратите внимание, как светофор циклически проходит через свои состояния по предсказуемой схеме: Red → Green → Yellow → Red. Каждое состояние знает, какое состояние следует за ним, и само управляет своим переходом. Классу TrafficLight не нужна никакая условная логика для определения того, что происходит — он просто делегирует выполнение тому состоянию, в котором он сейчас находится.
Шпаргалка
Паттерн Состояние (State) позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Вместо использования сложных условных операторов, каждое состояние инкапсулируется в виде отдельного класса.
Паттерн состоит из трех компонентов:
- Интерфейс State: Определяет поведение, специфичное для состояния
- Конкретные состояния (Concrete States): Реализуют поведение для каждого состояния
- Контекст (Context): Хранит ссылку на текущее состояние
Базовая структура:
#include <memory>
class Context; // Forward declaration
// State interface
class State {
public:
virtual void handleAction(Context& ctx) = 0;
virtual std::string getName() const = 0;
virtual ~State() = default;
};
// Context
class Context {
std::unique_ptr<State> state;
public:
void setState(std::unique_ptr<State> newState) {
state = std::move(newState);
}
void performAction() {
state->handleAction(*this);
}
std::string getStateName() const {
return state->getName();
}
};
// Concrete State
class ConcreteState : public State {
public:
void handleAction(Context& ctx) override {
// Handle action and transition to next state
ctx.setState(std::make_unique<AnotherState>());
}
std::string getName() const override {
return "ConcreteState";
}
};Каждое состояние обрабатывает действия по-разному и отвечает за переход к следующему состоянию. Контекст делегирует поведение текущему состоянию, не нуждаясь в знании логики переходов.
Используйте паттерн Состояние, когда поведение объекта сильно зависит от его состояния и вы хотите избежать громоздких условных операторов.
Попробуйте сами
#include <iostream>
#include "TrafficLight.h"
using namespace std;
int main() {
// Считать количество изменений состояния
int numChanges;
cin >> numChanges;
// TODO: Создать объект TrafficLight
// TODO: Отобразить начальное состояние
// TODO: Выполнить указанное количество изменений
// После каждого изменения отображать текущее состояние
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер