状态模式:让对象的行为随状态而变
状态模式,说白了就是让一个对象在内部状态改变时,它的行为也跟着变。你想想看,这就像一个人——高兴的时候哼着歌,生气的时候摔东西,疲惫的时候躺平。同一个对象,状态不同,行为完全不同。
我在项目中遇到过不少这样的场景。比如一个网络连接对象,它可能处于已连接、正在重连、已断开等状态。每个状态下,调用send()方法的行为都不一样。如果不用状态模式,你大概率会写一堆if-else或者switch-case,代码又臭又长,还容易漏掉某个状态分支。
核心思想:将每个状态的行为封装到独立的类中,让状态对象自己去决定该做什么。主对象(上下文)只负责委托给当前状态对象。
模式动机与定义
为什么需要状态模式?我个人的习惯是,当看到一个对象的行为随着内部状态变化而剧烈变化,而且状态数量可能增加时,就该考虑状态模式了。
举个例子,一个自动售货机:
- 待机状态:等待投币,投币后进入“已投币”状态
- 已投币状态:可以选择商品,选择后进入“出货中”状态
- 出货中状态:出货,出货完成后回到“待机状态”
每个状态下,用户的操作(投币、选择、取消)都有不同的响应。用状态模式,每个状态就是一个类,清晰又容易扩展。
定义:状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
我的经验:状态模式特别适合那些状态转换规则明确、状态数量有限的场景。我曾经在一个通信协议栈里用了状态模式,把几十个状态管理得井井有条。后来加新状态时,只需要新增一个类,改改转换规则,完全不影响现有代码。
角色与结构
状态模式涉及三个核心角色:
| 角色 | 名称 | 职责 |
|---|---|---|
| 上下文 | Context | 维护一个当前状态对象的引用,对外提供统一的接口 |
| 抽象状态 | State | 定义所有状态共有的接口,声明状态相关行为 |
| 具体状态 | ConcreteState | 实现具体状态下的行为,并决定何时转换到下一个状态 |
结构图如下:
你看这个结构,Context 里只存了一个 State 指针。每次调用 request(),它就委托给当前状态对象的 handle() 方法。状态对象内部可以改变 Context 的状态——说白了,就是把自己换成另一个状态对象。
C++ 代码实现
直接上代码。我习惯用 C++11 及以上标准,智能指针管理状态生命周期,避免裸指针的麻烦。
// 前向声明
class Context;
// 抽象状态类
class State {
public:
virtual ~State() = default;
virtual void handle(Context* ctx) = 0;
};
// 上下文类
class Context {
private:
std::unique_ptr<State> currentState;
public:
explicit Context(std::unique_ptr<State> state)
: currentState(std::move(state)) {}
void setState(std::unique_ptr<State> state) {
currentState = std::move(state);
}
void request() {
if (currentState) {
currentState->handle(this);
}
}
};
// 具体状态A
class ConcreteStateA : public State {
public:
void handle(Context* ctx) override {
std::cout << "状态A:处理请求,切换到状态B\n";
ctx->setState(std::make_unique<ConcreteStateB>());
}
};
// 具体状态B
class ConcreteStateB : public State {
public:
void handle(Context* ctx) override {
std::cout << "状态B:处理请求,切换到状态A\n";
ctx->setState(std::make_unique<ConcreteStateA>());
}
};
注意:状态对象内部调用 ctx->setState() 时,会销毁当前状态对象自身。这在 C++ 里是安全的——因为 handle() 执行完后,当前状态对象才被析构。但如果你在 handle() 之后还访问了当前对象的成员,那就出事了。我曾经踩过这个坑,后来养成了习惯:状态转换代码永远放在 handle() 的最后一行。
状态机示例:自动售货机
来个实际点的例子。自动售货机有三个状态:待机、已投币、出货中。每个状态下,用户的操作不同。
// 前向声明
class VendingMachine;
// 抽象状态
class MachineState {
public:
virtual ~MachineState() = default;
virtual void insertCoin(VendingMachine* vm) = 0;
virtual void selectProduct(VendingMachine* vm) = 0;
virtual void dispense(VendingMachine* vm) = 0;
};
// 自动售货机(上下文)
class VendingMachine {
private:
std::unique_ptr<MachineState> state;
int productCount = 5; // 剩余商品数量
public:
VendingMachine()
: state(std::make_unique<IdleState>()) {}
void setState(std::unique_ptr<MachineState> s) {
state = std::move(s);
}
void insertCoin() { state->insertCoin(this); }
void selectProduct() { state->selectProduct(this); }
void dispense() { state->dispense(this); }
int getProductCount() const { return productCount; }
void reduceProduct() { if (productCount > 0) productCount--; }
};
// 待机状态
class IdleState : public MachineState {
public:
void insertCoin(VendingMachine* vm) override {
std::cout << "投币成功,进入已投币状态\n";
vm->setState(std::make_unique<HasCoinState>());
}
void selectProduct(VendingMachine*) override {
std::cout << "请先投币\n";
}
void dispense(VendingMachine*) override {
std::cout << "请先投币并选择商品\n";
}
};
// 已投币状态
class HasCoinState : public MachineState {
public:
void insertCoin(VendingMachine*) override {
std::cout << "已经投过币了,请选择商品\n";
}
void selectProduct(VendingMachine* vm) override {
if (vm->getProductCount() > 0) {
std::cout << "选择商品成功,进入出货中状态\n";
vm->setState(std::make_unique<DispensingState>());
} else {
std::cout << "商品已售罄,退回硬币\n";
vm->setState(std::make_unique<IdleState>());
}
}
void dispense(VendingMachine*) override {
std::cout << "请先选择商品\n";
}
};
// 出货中状态
class DispensingState : public MachineState {
public:
void insertCoin(VendingMachine*) override {
std::cout << "正在出货,请稍候\n";
}
void selectProduct(VendingMachine*) override {
std::cout << "正在出货,请稍候\n";
}
void dispense(VendingMachine* vm) override {
vm->reduceProduct();
std::cout << "出货完成,回到待机状态\n";
vm->setState(std::make_unique<IdleState>());
}
};
使用起来也很直观:
int main() {
VendingMachine vm;
vm.insertCoin(); // 投币成功,进入已投币状态
vm.selectProduct(); // 选择商品成功,进入出货中状态
vm.dispense(); // 出货完成,回到待机状态
vm.dispense(); // 请先投币并选择商品
return 0;
}
避坑指南:我曾经在一个项目中,状态转换逻辑散落在各个状态类的 handle 方法里,结果状态图一复杂,转换关系就乱成一团。后来我学乖了——把状态转换表单独抽出来,或者用状态图工具先画清楚再编码。状态模式虽然灵活,但状态转换的维护成本不低,建议配合状态图一起使用。
状态模式的核心价值,就是把变化的行为从上下文里剥离出来,让每个状态自己管自己。你想想看,如果以后要加一个“退款中”状态,只需要新增一个类,改改转换规则,完全不用动 VendingMachine 的代码。这就是开闭原则的体现——对扩展开放,对修改关闭。
嗯,状态模式就聊到这儿。记住一点:状态多、行为杂、转换规则明确——这三个条件满足时,状态模式就是你的好帮手。