Menu
Coddy logo textTech

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

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

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

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

#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() action и отвечает за переход к следующему состоянию. Document не нужно знать логику перехода — оно просто делегирует её состоянию current. Это устраняет большие операторы switch и упрощает добавление новых состояний.

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

challenge icon

Задание

Легко

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

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

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

    Создайте абстрактный класс TrafficLightState со следующими элементами:

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

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

    • RedState: цвет — "Red", действие — "Stop", переход к Green;
    • YellowState: цвет — "Yellow", действие — "Caution", переход к Red;
    • GreenState: цвет — "Green", действие — "Go", переход к Yellow.

    Вам потребуется предварительное объявление TrafficLight, поскольку состояния ссылаются на него.

  • TrafficLight.h: создайте класс контекста, который хранит текущее состояние.

    Ваш класс TrafficLight должен содержать std::unique_ptr<TrafficLightState> и по умолчанию начинать работу в состоянии Red. Реализуйте:

    • setState(std::unique_ptr<TrafficLightState> newState): изменяет текущее состояние;
    • change(): делегирует выполнение методу change текущего состояния;
    • display(): выводит статус светофора в формате: [Color]: [Action].
  • main.cpp: продемонстрируйте циклический переход светофора между состояниями.

    Считайте одно входное значение: количество изменений состояния, которые необходимо выполнить (целое число).

    Создайте объект TrafficLight и отобразите его initial состояние. Затем выполните указанное количество изменений, отображая состояние после каждого изменения.

Например, при вводе 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Проверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++