简单来说,**状态模式(State Pattern)**就是为了解决代码中那堆令人头疼的 if-else 或 switch-case。当一个对象的行为取决于它的"状态"时,我们干脆把每种状态都封装成一个独立的类。
场景设定:自动贩卖机
想象一个简单的自动贩卖机,它有两个状态:
-
NoMoney(待投币)
-
HasMoney(已投币)
动作有两个:投币 、退币。
1. 不使用模式:基于 switch-case 的硬编码
这种方式下,所有的逻辑都堆在一个类里,每增加一个动作或状态,都要修改所有现有的函数。
#include <iostream>
enum State { NO_MONEY, HAS_MONEY, SOLD_OUT };
class VendingMachine {
private:
State state = NO_MONEY;
public:
void insertMoney() {
switch (state) {
case NO_MONEY:
std::cout << "投币成功!" << std::endl;
state = HAS_MONEY;
break;
case HAS_MONEY:
std::cout << "已有硬币,请勿重复投币。" << std::endl;
break;
case SOLD_OUT:
std::cout << "已售罄,投币失败。" << std::endl;
break;
}
}
void ejectMoney() {
switch (state) {
case HAS_MONEY:
std::cout << "退币成功。" << std::endl;
state = NO_MONEY;
break;
case NO_MONEY:
std::cout << "没钱可退。" << std::endl;
break;
case SOLD_OUT:
std::cout << "抱歉,没投币也没货了。" << std::endl;
break;
}
}
};
int main() {
VendingMachine machine;
machine.insertMoney();
machine.ejectMoney();
return 0;
}
2. 使用状态模式:面向对象的状态迁移
这种方式将"状态"抽离成类。VendingMachine 只负责转发请求,具体的逻辑由当前的状态对象自己处理。
#include <iostream>
#include <memory>
class VendingMachine;
// 1. 抽象状态类
class State {
public:
virtual ~State() = default;
virtual void insertMoney(VendingMachine& machine) = 0;
virtual void ejectMoney(VendingMachine& machine) = 0;
};
// 2. 环境类(贩卖机)
class VendingMachine {
private:
std::unique_ptr<State> currentState;
public:
VendingMachine(std::unique_ptr<State> initialState)
: currentState(std::move(initialState)) {}
void setState(std::unique_ptr<State> newState) {
currentState = std::move(newState);
}
void insertMoney() { currentState->insertMoney(*this); }
void ejectMoney() { currentState->ejectMoney(*this); }
};
// 3. 具体状态:已投币
class HasMoneyState : public State {
public:
void insertMoney(VendingMachine& machine) override {
std::cout << "错误:已有硬币。" << std::endl;
}
void ejectMoney(VendingMachine& machine) override;
};
// 4. 具体状态:待投币
class NoMoneyState : public State {
public:
void insertMoney(VendingMachine& machine) override {
std::cout << "投币成功!" << std::endl;
// 状态切换逻辑:交给具体类处理
machine.setState(std::make_unique<HasMoneyState>());
}
void ejectMoney(VendingMachine& machine) override {
std::cout << "错误:没钱可退。" << std::endl;
}
};
// 实现 HasMoneyState 的延迟定义
void HasMoneyState::ejectMoney(VendingMachine& machine) {
std::cout << "退币成功!" << std::endl;
machine.setState(std::make_unique<NoMoneyState>());
}
int main() {
// 初始状态为"待投币"
VendingMachine machine(std::make_unique<NoMoneyState>());
machine.insertMoney(); // 投币
machine.ejectMoney(); // 退币
return 0;
}
对比总结
| 特性 | 硬编码 (switch-case) | 状态模式 (State Pattern) |
|---|---|---|
| 逻辑分布 | 逻辑集中在一个类,极其臃肿。 | 逻辑分散在具体状态类,符合单一职责。 |
| 扩展性 | 差。增加状态需要改动所有函数源码。 | 优。增加状态只需新建一个类。 |
| 可读性 | 分支越多,代码越像"屎山"。 | 逻辑清晰,像是在阅读状态转移图。 |
| 适用场景 | 状态极少(2-3个)且永远不会变。 | 状态多、逻辑复杂、经常需要扩展。 |
假设老板突然加了一个新需求:"增加一个'中奖'功能。投币后有 10% 的概率中奖,中奖后直接出货,不需要按按钮。"
1. 不使用模式:在 switch-case 里"动手术"
你必须深入到原有的每一个动作函数里,小心翼翼地插入 if 判断。
void VendingMachine::insertMoney() {
switch (state) {
case NO_MONEY:
std::cout << "投币成功!" << std::endl;
// --- 新增需求逻辑:硬塞进去 ---
if (rand() % 10 == 0) {
std::cout << "恭喜中奖!自动出货。" << std::endl;
state = NO_MONEY; // 中奖完直接回到初始
} else {
state = HAS_MONEY;
}
// --------------------------
break;
case HAS_MONEY:
// ... 原有逻辑 ...
break;
}
}
风险: 如果你的代码有 1000 行,你得满世界找哪里需要加这个 if。一旦漏掉一个地方,逻辑就出 Bug 了(比如中奖了结果还能退币)。
2. 使用状态模式:新增一个"状态类"
你完全不需要修改 VendingMachine 类,也不需要修改 NoMoneyState。你只需要像插拔 U 盘一样,增加一个新的逻辑块。
第一步:新建一个状态类(或者修改现有的特定状态) 我们只需要在 NoMoneyState(投币那个瞬间)做文章:
void NoMoneyState::doInsert(VendingMachine* vm) {
std::cout << "投币成功!" << std::endl;
if (rand() % 10 == 0) {
std::cout << "恭喜中奖!" << std::endl;
// 逻辑直接跳过"已投币",执行出货逻辑,最后回到自己
vm->changeState(new NoMoneyState());
} else {
vm->changeState(new HasMoneyState());
}
}
但当你的需求变成:
- 投币成功 -> 2. 选择商品 -> 3. 检查库存 -> 4. 出货中 -> 5. 故障处理 -> 6. 维护模式...
这时候,状态模式 就像是把一团乱麻拆成了 一格一格的抽屉。
-
以后想改"故障处理"的逻辑?直接拉开那个抽屉改就行。
-
其他抽屉(投币、出货)动都不用动,绝对不会被你改出 Bug。
一句话总结:
-
硬编码 是把逻辑写在动作里(投币时判断状态)。
-
状态模式 是把逻辑写在状态里(在没钱状态下处理投币)。