Menu
CoddyTech

状态模式

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 100 课,共 104 课。

状态模式允许对象在内部状态改变时改变行为,让它看起来像是改变了类。你可以将每种状态封装为一个单独的类,而不是使用复杂的条件语句来处理不同状态。

该模式由一个上下文、一个状态接口和多个具体状态组成。上下文保存对当前状态的引用,状态接口定义特定于状态的行为,具体状态则实现每个状态对应的行为:

#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

挑战

简单

让我们使用状态模式构建一个交通信号灯控制器。你将创建一个系统,让交通信号灯在不同状态(红、黄、绿)之间循环,每种状态都会决定信号灯变化时会发生什么,以及显示什么消息。这是状态模式的经典示例:交通信号灯的行为完全取决于它当前所处的状态。

你将把代码分成 3 个文件:

  • TrafficLightState.h:定义状态接口和所有具体状态类。

    创建一个抽象的 TrafficLightState 类,包含:

    • 一个纯虚方法 change(TrafficLight& light),用于处理切换到下一个状态
    • 一个纯虚方法 getColor(),以字符串形式返回当前颜色
    • 一个纯虚方法 getAction(),返回驾驶员应该采取的行动(例如,"Stop"、"Caution"、"Go")
    • 一个虚析构函数

    实现 3 个具体状态:

    • 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,并显示其初始状态。然后执行指定次数的状态变化,每次变化后都显示状态。

例如,输入为 3 时:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop

输入为 6 时:

Red: Stop
Green: Go
Yellow: Caution
Red: Stop
Green: Go
Yellow: Caution
Red: Stop

注意,交通信号灯会按照可预测的模式在各个状态之间循环:红 → 绿 → 黄 → 红。每种状态都知道下一个状态是什么,并负责处理自己的状态切换。TrafficLight 类不需要使用任何条件逻辑来判断接下来会发生什么,只需委托给它当前所处的状态即可。

亲手试试

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

using namespace std;

int main() {
    // 读取状态变化的次数
    int numChanges;
    cin >> numChanges;
    
    // TODO: 创建一个 TrafficLight 对象
    
    // TODO: 显示初始状态
    
    // TODO: 按指定次数更改状态
    // 每次更改后,显示当前状态
    
    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器