状态模式
本课属于 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 语句,也让添加新状态变得简单。
当对象的行为高度依赖其状态,并且你有许多根据对象状态进行切换的条件语句时,可以使用状态模式。
挑战
简单让我们使用状态模式构建一个交通信号灯控制器。你将创建一个系统,让交通信号灯在不同状态(红、黄、绿)之间循环,每种状态都会决定信号灯变化时会发生什么,以及显示什么消息。这是状态模式的经典示例:交通信号灯的行为完全取决于它当前所处的状态。
你将把代码分成 3 个文件:
TrafficLightState.h:定义状态接口和所有具体状态类。创建一个抽象的
TrafficLightState类,包含:- 一个纯虚方法
change(TrafficLight& light),用于处理切换到下一个状态 - 一个纯虚方法
getColor(),以字符串形式返回当前颜色 - 一个纯虚方法
getAction(),返回驾驶员应该采取的行动(例如,"Stop"、"Caution"、"Go") - 一个虚析构函数
实现 3 个具体状态:
RedState:颜色为"Red",行动为"Stop",切换到 GreenYellowState:颜色为"Yellow",行动为"Caution",切换到 RedGreenState:颜色为"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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器