Menu
Coddy logo textTech

Паттерн Состояние

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 100 из 104.

Паттерн Состояние (State) позволяет объекту изменять свое поведение при изменении его внутреннего состояния, создавая впечатление, что объект сменил свой класс. Вместо использования сложных условных операторов для обработки различных состояний, вы инкапсулируете каждое состояние в виде отдельного класса.

Паттерн состоит из Context, который хранит ссылку на текущее состояние, State interface, определяющего поведение, специфичное для состояния, и Concrete States, которые реализуют поведение для каждого состояния:

#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 и делает добавление новых состояний простым процессом.

Используйте Состояние, когда поведение объекта сильно зависит от его состояния и у вас есть много условных операторов, переключающихся в зависимости от состояния объекта.

challenge icon

Задание

Легко

Давайте создадим Контроллер светофора, используя паттерн Состояние (State). Вы создадите систему, в которой светофор циклически переключается между различными состояниями (Red, Yellow, Green), при этом каждое состояние определяет, что происходит при смене сигнала и какое сообщение выводится на экран. Это классический пример паттерна Состояние — поведение светофора полностью зависит от его текущего состояния.

Вы организуете свой код в трех файлах:

  • TrafficLightState.h: Определите интерфейс состояния и все конкретные классы состояний.

    Создайте абстрактный класс TrafficLightState с:

    • Чисто виртуальным методом change(TrafficLight& light), который обрабатывает переход к следующему состоянию
    • Чисто виртуальным методом getColor(), который возвращает текущий цвет в виде строки
    • Чисто виртуальным методом getAction(), который возвращает то, что должны делать водители (например, "Stop", "Caution", "Go")
    • Виртуальным деструктором

    Реализуйте три конкретных состояния:

    • RedState — цвет "Red", действие "Stop", меняется на Green
    • YellowState — цвет "Yellow", действие "Caution", меняется на Red
    • GreenState — цвет "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 не нужна условная логика для определения того, что происходит — он просто делегирует задачу тому состоянию, в котором он сейчас находится.

Попробуйте сами

#include <iostream>
#include "TrafficLight.h"

using namespace std;

int main() {
    // Считать количество изменений состояния
    int numChanges;
    cin >> numChanges;
    
    // TODO: Создать объект TrafficLight
    
    // TODO: Отобразить начальное состояние
    
    // TODO: Выполнить указанное количество изменений
    // После каждого изменения отображать текущее состояние
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование