一、问题背景
在现实生活中,每个人或事物在不同的状态下会有不同的表现(动作),而一个状态又会在不同的表现下转移到下一个不同的状态。例如,在地铁入口处,如果你放入正确的地铁票,门就会打开让你通过;在出口处,如果验票正确,你就可以通过,否则就不让你通过(如果动作野蛮,可能会触发报警)。
在软件系统中,有限状态自动机(FSM)也是一个典型的状态不同,对输入有不同的响应(状态转移)。通常我们在实现这类系统时会使用大量的 `Switch/Case` 语句来处理不同的状态和动作。然而,这种实现方式存在以下两个问题:
(1)维护困难:当状态数目较少时,`Switch/Case` 语句可能还能应付,但当状态数目增多时,维护大量的 `Switch/Case` 语句将变得异常困难且容易出错。
(2)耦合度高:状态逻辑和动作实现没有分离。在很多系统实现中,动作的实现代码直接写在状态的逻辑中,导致系统的扩展性和维护性得不到保证。
二、模式选择及实现
状态模式(State Pattern) 就是用来解决上述问题的。在状态模式中,我们将状态逻辑和动作实现进行分离。当一个操作中要维护大量的 `case` 分支语句,并且这些分支依赖于对象的状态时,状态模式将每一个分支都封装到独立的类中。
状态模式的结构图
实现
完整代码示例
以下是使用 C++ 实现状态模式的完整代码示例。
代码片段 1:State.h
// State.h
#ifndef _STATE_H_
#define _STATE_H_
class Context; // 前置声明
// State 类是所有状态类的基类,定义了状态接口
class State {
public:
State() = default;
virtual ~State() = default;
// 状态操作接口,由具体状态类实现
virtual void OperationInterface(Context*) = 0;
// 状态转换操作接口,由具体状态类实现
virtual void OperationChangeState(Context*) = 0;
protected:
// 改变状态的方法,由具体状态类调用
bool ChangeState(Context* con, State* st);
private:
// 私有成员和方法(如果有)
};
// 具体状态类 A
class ConcreteStateA : public State {
public:
ConcreteStateA() = default;
~ConcreteStateA() override = default;
void OperationInterface(Context*) override;
void OperationChangeState(Context*) override;
private:
// 私有成员和方法(如果有)
};
// 具体状态类 B
class ConcreteStateB : public State {
public:
ConcreteStateB() = default;
~ConcreteStateB() override = default;
void OperationInterface(Context*) override;
void OperationChangeState(Context*) override;
private:
// 私有成员和方法(如果有)
};
#endif //~_STATE_H_
代码片段 2:State.cpp
// State.cpp
#include "State.h"
#include "Context.h"
#include <iostream>
using namespace std;
// State 类的默认实现
State::State() {}
State::~State() {}
// 改变状态的方法实现
bool State::ChangeState(Context* con, State* st) {
con->ChangeState(st);
return true;
}
// ConcreteStateA 类的实现
ConcreteStateA::ConcreteStateA() {}
ConcreteStateA::~ConcreteStateA() {}
// ConcreteStateA 的状态操作接口实现
void ConcreteStateA::OperationInterface(Context* con) {
cout << "ConcreteStateA::OperationInterface......" << endl;
}
// ConcreteStateA 的状态转换操作接口实现
void ConcreteStateA::OperationChangeState(Context* con) {
OperationInterface(con);
this->ChangeState(con, new ConcreteStateB());
}
// ConcreteStateB 类的实现
ConcreteStateB::ConcreteStateB() {}
ConcreteStateB::~ConcreteStateB() {}
// ConcreteStateB 的状态操作接口实现
void ConcreteStateB::OperationInterface(Context* con) {
cout << "ConcreteStateB::OperationInterface......" << endl;
}
// ConcreteStateB 的状态转换操作接口实现
void ConcreteStateB::OperationChangeState(Context* con) {
OperationInterface(con);
this->ChangeState(con, new ConcreteStateA());
}
代码片段 3:Context.h
// Context.h
#ifndef _CONTEXT_H_
#define _CONTEXT_H_
class State;
// Context 类持有当前状态,并委托状态类执行操作
class Context {
public:
Context();
explicit Context(State* state); // 显式构造函数
~Context();
// 执行当前状态的操作接口
void OperationInterface();
// 执行当前状态的状态转换操作
void OperationChangeState();
private:
friend class State; // 允许 State 类访问 Context 类的私有成员
// 改变当前状态
bool ChangeState(State* state);
State* _state; // 当前状态
};
#endif //~_CONTEXT_H_
代码片段 4:Context.cpp
// Context.cpp
#include "Context.h"
#include "State.h"
// Context 类的默认构造函数
Context::Context() : _state(nullptr) {}
// Context 类的带参构造函数
Context::Context(State* state) : _state(state) {}
// Context 类的析构函数
Context::~Context() {
delete _state;
}
// 执行当前状态的操作接口
void Context::OperationInterface() {
if (_state) {
_state->OperationInterface(this);
}
}
// 改变当前状态
bool Context::ChangeState(State* state) {
if (_state != state) {
delete _state;
_state = state;
}
return true;
}
// 执行当前状态的状态转换操作
void Context::OperationChangeState() {
if (_state) {
_state->OperationChangeState(this);
}
}
代码片段 5:main.cpp
// main.cpp
#include "Context.h"
#include "State.h"
#include <iostream>
using namespace std;
// 主函数
int main(int argc, char* argv[]) {
State* st = new ConcreteStateA();
Context* con = new Context(st);
// 执行操作接口
con->OperationInterface();
con->OperationInterface();
con->OperationInterface();
// 清理资源
delete con;
st = nullptr;
return 0;
}
代码说明
在状态模式的实现中,有两个关键点:
(1)友元类:将 `State` 声明为 `Context` 的友元类(`friend class`),其作用是让 `State` 类可以访问 `Context` 的 `protected` 接口 `ChangeState()`。
(2)状态类持有 Context 指针:`State` 及其子类中的操作都将 `Context*` 传入作为参数,其主要目的是 `State` 类可以通过这个指针调用 `Context` 中的方法。这也是状态模式和策略模式的最大区别所在。
运行示例代码后,可以获得以下结果:连续 3 次调用了 `Context` 的 `OprationInterface()`,因为每次调用后状态都会改变(A → B → A),因此该动作随着 `Context` 的状态的转变而获得了不同的结果。
三、总结讨论
状态模式的应用场景主要包括:
(1)行为依赖状态:对象行为随状态动态变化时,如电梯、订单状态。
(2)消除条件分支:减少大量条件语句,如游戏角色行为、交通灯状态。
(3)复杂状态转换:状态转换逻辑复杂时,如工作流任务、线程生命周期。
(4)多状态共存:对象同时处于多个状态时,如文件状态、用户权限。
(5)状态与行为解耦:将状态和行为分离,如网络连接、播放器状态。
(6)状态历史记录:支持状态记录和回滚,如文本编辑器撤销、事务回滚。
(7)状态可扩展性:方便添加新状态,如游戏角色状态、设备状态。
状态模式通过将状态逻辑和动作实现分离,使得系统更加灵活和易于扩展。尽管状态模式在某些情况下可能会导致逻辑分散化,但在处理复杂的状态转换逻辑时,它仍然是一个非常有效的设计模式。
状态模式与策略模式的比较
状态模式和策略模式在结构上有很大的相似性:它们都有一个 `Context` 类,都是通过委托(组合)给一个具有多个派生类的多态基类来实现 `Context` 的算法逻辑。然而,两者最大的区别在于:
(1)状态模式:派生类持有指向 `Context` 对象的引用,并通过这个引用调用 `Context` 中的方法。状态模式主要关注对象在不同状态下的行为变化。
(2)策略模式:没有状态的概念,主要关注算法的替换,`Context` 类通过组合不同的 `Strategy` 对象来动态改变行为。
状态模式的优缺点
优点:
状态模式很好地实现了对象的状态逻辑和动作实现的分离,状态逻辑分布在 `State` 的派生类中实现,而动作实现则可以放在 `Context` 类中实现。这使得两者的变化相互独立,易于扩展和维护。
缺点:
状态逻辑分散化,状态逻辑分布到了很多的 `State` 的子类中,很难看到整个的状态逻辑图,这也带来了代码的维护问题。