定义
状态模式(State Pattern)是一种行为设计模式。它允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类,从直观上看,就像是对象根据自身的状态来动态地切换行为方式。
结构组成
- 环境(Context)类:
环境类是拥有状态的对象,它维护一个具体状态类的实例,这个实例代表当前对象的状态。
它定义了客户感兴趣的接口,并且负责具体状态的切换。例如,在一个简单的自动售货机系统中,自动售货机就是环境类,它有 "已投币""未投币" 等状态。 - 抽象状态(State)类:
这是所有具体状态类的基类,它定义了一个接口,用于封装与环境类的一个特定状态相关的行为。比如,在上述自动售货机例子中,抽象状态类可以定义像 "选择商品""退币" 等行为接口,这些接口的具体实现由具体状态类来完成。 - 具体状态(Concrete State)类:
具体状态类实现了抽象状态类中定义的接口。每个具体状态类对应环境类的一种状态,并且实现了在该状态下环境类应该具有的行为。以自动售货机为例,"未投币状态" 具体类会实现 "投币" 接口行为,当执行投币操作后,环境类(自动售货机)的状态可能会切换到 "已投币状态"。
工作原理
当环境类的内部状态发生改变时,它会调用当前状态对象的方法。这些方法的具体实现是在具体状态类中定义的。
例如,假设有一个文档编辑软件,文档可以处于 "只读" 和 "可编辑" 两种状态。文档对象(环境类)维护一个状态对象(具体是 "只读状态" 对象或者 "可编辑状态" 对象)。当用户试图修改文档内容时,如果文档处于 "可编辑" 状态,修改操作会正常进行;如果文档处于 "只读" 状态,修改操作会被禁止,可能会弹出一个提示框告知用户文档是只读的。这就是状态模式根据不同状态执行不同行为的体现。
代码示例
电灯
以下是一个简单的 C++ 状态模式示例,以电灯的开关状态为例。
- 首先是抽象状态类:
cpp
class LightState {
public:
virtual void handle(Light* light) = 0;
};
- 然后是具体状态类,比如 "开状态":
cpp
class OnState : public LightState {
public:
void handle(Light* light) override {
std::cout << "灯已经是开着的。" << std::endl;
}
};
- 还有 "关状态":
cpp
class OffState : public LightState {
public:
void handle(Light* light) override {
std::cout << "灯已经是关着的。" << std::endl;
}
};
接着是环境类(电灯类):
cpp
class Light {
private:
LightState* state;
public:
Light() {
state = new OffState();
}
void setState(LightState* newState) {
state = newState;
}
void request() {
state->handle(this);
}
};
- 使用示例:
cpp
int main() {
Light light;
light.request();
LightState* on = new OnState();
light.setState(on);
light.request();
return 0;
}
在这个示例中,Light类是环境类,LightState是抽象状态类,OnState和OffState是具体状态类。Light类的request方法根据当前的状态(state对象)来执行不同的行为,通过setState方法可以切换状态。
交通信号灯系统
- 状态描述:
交通信号灯有 "红灯""绿灯""黄灯" 三种状态,并且会按照一定的规则进行状态转换。
环境(Context)类 - 交通信号灯类(TrafficLight):
它维护交通信号灯的当前状态对象引用,并且有一个change()方法用于状态转换。
cpp
class TrafficLight {
private:
TrafficLightState* state;
public:
TrafficLight() {
state = new RedLightState();
}
void setState(TrafficLightState* newState) {
state = newState;
}
void change() {
state->change(this);
}
};
- 抽象状态(State)类 - 交通信号灯状态(TrafficLightState):
定义了change()抽象方法来处理交通信号灯状态转换时的行为。
cpp
class TrafficLightState {
public:
virtual void change(TrafficLight* trafficLight) = 0;
};
- 具体状态(Concrete State)类举例 - 红灯状态(RedLightState):
当交通信号灯处于 "红灯" 状态时,车辆停止,行人可以通过。并且在一定时间后,状态会转换为 "绿灯"。
cpp
class RedLightState : public TrafficLightState {
public:
void change(TrafficLight* trafficLight) override {
std::cout << "红灯亮,车辆停止,行人通过。" << std::endl;
// 模拟时间延迟后转换状态
std::this_thread::sleep_for(std::chrono::seconds(5));
TrafficLightState* greenState = new GreenLightState();
trafficLight->setState(greenState);
}
};
- 具体状态(Concrete State)类举例 - 绿灯状态(GreenLightState):
当交通信号灯处于 "绿灯" 状态时,车辆可以通过,行人停止。一段时间后转换为 "黄灯"。
cpp
class GreenLightState : public TrafficLightState {
public:
void change(TrafficLight* trafficLight) override {
std::cout << "绿灯亮,车辆通过,行人停止。" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
TrafficLightState* yellowState = new YellowLightState();
trafficLight->setState(yellowState);
}
};
- 具体状态(Concrete State)类举例 - 黄灯状态(YellowLightState):
当交通信号灯处于 "黄灯" 状态时,车辆减速,准备停车。之后转换为 "红灯"。
cpp
class YellowLightState : public TrafficLightState {
public:
void change(TrafficLight* trafficLight) override {
std::cout << "黄灯亮,车辆减速。" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
TrafficLightState* redState = new RedLightState();
trafficLight->setState(redState);
}
};
- 状态转换示例:
模拟交通信号灯的状态转换过程。
cpp
int main() {
TrafficLight trafficLight;
for (int i = 0; i < 10; ++i) {
trafficLight.change();
}
return 0;
}
优点
- 提高可维护性:
将不同状态下的行为封装到不同的状态类中,使得代码结构更加清晰。当需要修改某个状态下的行为时,只需要修改对应的具体状态类,而不会影响到其他状态的代码。例如,在一个复杂的游戏角色状态系统中,角色有 "行走""攻击""防御" 等状态,使用状态模式后,如果要修改 "攻击" 状态下的行为逻辑,只需要在 "攻击状态" 具体类中修改即可。 - 符合开闭原则:
可以很容易地增加新的状态类来扩展系统功能。比如,在上述游戏角色状态系统中,如果要添加一个 "施法" 状态,只需要创建一个新的 "施法状态" 具体类,实现相应的行为接口,然后在环境类(游戏角色类)中进行适当的状态切换处理即可,不需要修改原有的状态类代码。
缺点
- 增加类的数量:
每一个状态都需要一个具体状态类来实现,会导致系统中类的数量增加。在一个状态较多的复杂系统中,可能会产生大量的状态类,使得代码的管理和理解变得困难。例如,一个具有几十种不同状态的工业控制系统,如果使用状态模式,会有大量的状态类文件,增加了代码的复杂性。 - 系统结构复杂:
状态模式的实现涉及到环境类、抽象状态类和多个具体状态类之间的交互,对于不熟悉设计模式的开发人员来说,理解和实现起来可能会有一定的难度。特别是在状态转换逻辑比较复杂的情况下,可能会出现状态转换错误等问题。