📕目录
1. 环境类(Context):状态的“持有者”和“切换器”
3. 具体状态类(Concrete State):状态行为的“实现者”

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
前言

我们总会遇到这样的场景:一个对象的行为会随着它内部状态的变化而变化,就像手机有飞行模式、静音模式、振动模式,不同模式下接收到消息的反应完全不同。如果用传统的if-else或者switch-case来处理这些状态逻辑,代码会变得臃肿不堪,新增状态时更是要“动一发而牵全身”,维护性直接跌入谷底。
今天咱们要聊的状态模式,就是解决这类“状态驱动行为”问题的利器。它能让状态逻辑和行为实现分离,让代码像搭积木一样灵活可扩展。本文会从生活场景切入,带你吃透状态模式的核心思想,再通过完整的C++实战案例,手把手教你在项目中落地,全是能直接复用的干货。
一、从自动售货机说起:为什么需要状态模式?

在讲状态模式的定义之前,咱们先从一个你每天都可能接触的东西——自动售货机,聊聊状态逻辑的复杂性。一台普通的自动售货机,至少有这么几种状态:
-
待机状态:没投币,只能选择商品看价格,不能出货
-
投币状态:已经给钱了,能选择对应价格的商品,选对了就出货
-
缺货状态:某个商品卖完了,就算投币也选不了
-
找零状态:投的钱比商品贵,出货后还要吐零钱
如果让你用C++写一个自动售货机的控制逻辑,不用设计模式的话,大概率会写出这样的代码:
#include <iostream>
#include <string>
using namespace std;
// 自动售货机类
class VendingMachine {
private:
// 状态枚举:待机、投币、缺货、找零
enum State { STANDBY, COIN_INSERTED, OUT_OF_STOCK, GIVE_CHANGE };
State currentState; // 当前状态
int balance; // 余额
int productPrice; // 商品价格
bool isProductAvailable; // 商品是否可用
public:
VendingMachine() {
currentState = STANDBY;
balance = 0;
productPrice = 3;
isProductAvailable = true;
}
// 投币操作
void insertCoin(int money) {
switch (currentState) {
case STANDBY:
balance += money;
cout << "投币成功,当前余额:" << balance << endl;
currentState = COIN_INSERTED; // 切换到投币状态
break;
case COIN_INSERTED:
balance += money;
cout << "已投币,追加投币成功,当前余额:" << balance << endl;
break;
case OUT_OF_STOCK:
cout << "商品缺货,无法投币,请取走钱币" << endl;
break;
case GIVE_CHANGE:
cout << "正在找零,请先取走零钱" << endl;
break;
default:
cout << "未知状态" << endl;
}
}
// 选择商品操作
void selectProduct() {
switch (currentState) {
case STANDBY:
cout << "请先投币" << endl;
break;
case COIN_INSERTED:
if (!isProductAvailable) {
cout << "商品缺货,即将为您退币" << endl;
balance = 0;
currentState = STANDBY;
break;
}
if (balance >= productPrice) {
cout << "商品出货中,请取走商品" << endl;
balance -= productPrice;
if (balance > 0) {
currentState = GIVE_CHANGE; // 切换到找零状态
} else {
currentState = STANDBY; // 余额为0,回到待机
}
} else {
cout << "余额不足,当前余额:" << balance << ",商品价格:" << productPrice << endl;
}
break;
case OUT_OF_STOCK:
cout << "商品缺货,无法选择" << endl;
break;
case GIVE_CHANGE:
cout << "正在找零,请先取走零钱" << endl;
break;
default:
cout << "未知状态" << endl;
}
}
// 取走零钱操作
void takeChange() {
switch (currentState) {
case STANDBY:
cout << "当前无零钱可取" << endl;
break;
case COIN_INSERTED:
cout << "请先选择商品或取消购买" << endl;
break;
case OUT_OF_STOCK:
cout << "当前无零钱可取" << endl;
break;
case GIVE_CHANGE:
cout << "已取走零钱:" << balance << endl;
balance = 0;
currentState = STANDBY; // 回到待机状态
break;
default:
cout << "未知状态" << endl;
}
}
// 补充商品操作
void replenishProduct() {
isProductAvailable = true;
cout << "商品补充完成,当前商品可售" << endl;
// 如果之前是缺货状态,补充后回到待机
if (currentState == OUT_OF_STOCK) {
currentState = STANDBY;
}
}
};
// 测试代码
int main() {
VendingMachine vm;
vm.selectProduct(); // 未投币选商品
vm.insertCoin(2); // 投2元(不足)
vm.selectProduct(); // 余额不足
vm.insertCoin(2); // 再投2元,共4元
vm.selectProduct(); // 出货,余额1元(进入找零状态)
vm.selectProduct(); // 找零状态下选商品
vm.takeChange(); // 取走1元零钱
vm.insertCoin(3); // 投3元
// 模拟缺货
vm.replenishProduct(); // 先补充(测试用)
// 这里手动设置缺货,实际中应该是库存为0的逻辑
((void)0); // 占位,实际项目中可通过库存变量控制
vm.selectProduct(); // 假设此时缺货
return 0;
}
这段代码能跑起来,但如果你是维护者,看到这样的代码会不会头皮发麻?它的问题太明显了:
-
状态逻辑混乱:所有状态的判断都揉在switch-case里,投币、选商品、找零的逻辑相互交织,想改一个状态的行为都要翻遍所有相关方法。
-
违反开闭原则:如果要新增“维护状态”(此时不能投币、不能选商品),就要在每个switch-case里都加一个case分支,改动量大且容易出错。
-
可读性差:新人接手时,要理清每个状态下每个操作的逻辑,得逐行读代码,效率极低。
-
可维护性低:某个状态的逻辑出问题,比如投币后余额计算错误,可能要修改多个地方,排查成本高。
而状态模式就是为解决这类“对象状态驱动行为”的问题而生的。它的核心思路很简单:把每个状态的行为封装成独立的类,让对象的状态变化时,直接切换对应的状态类,而不是通过一堆条件判断。就像自动售货机,每个状态(待机、投币等)都像一个“功能模块”,切换状态就是换模块,不用动核心逻辑。
二、状态模式核心解析:3个角色+1个核心思想

在正式讲状态模式的定义前,咱们先明确它的核心组成。根据《设计模式:可复用面向对象软件的基础》中的定义,状态模式包含三个核心角色,这三个角色协同工作,实现“状态决定行为”的效果。
2.1 状态模式的三个核心角色
咱们结合自动售货机的例子,一个个拆解这三个角色,保证你一看就懂:
1. 环境类(Context):状态的“持有者”和“切换器”
环境类就是那个拥有状态的对象,比如咱们的自动售货机。它的核心职责有两个:
-
持有一个当前状态的引用(比如“当前处于投币状态”);
-
提供对外的操作接口(比如投币、选商品),并将这些操作委托给当前的状态类去处理;
-
负责状态之间的切换(比如投币后从“待机状态”切换到“投币状态”)。
简单说,环境类是“门面”,对外提供统一的操作入口,对内则把具体工作交给状态类。
2. 抽象状态类(State):状态行为的“规范”
抽象状态类是所有具体状态的父类,它定义了每个状态应该具备的行为接口。比如自动售货机的所有状态,都需要处理“投币”“选商品”“取零钱”这几个操作,所以抽象状态类就要定义这几个纯虚函数,让具体状态类去实现。
这样做的好处是:环境类只需要依赖抽象状态类,不用关心具体是哪个状态,符合“依赖倒置原则”(依赖抽象,不依赖具体)。
3. 具体状态类(Concrete State):状态行为的“实现者”
每个具体状态类对应一个实际的状态,比如“待机状态类”“投币状态类”。它们实现抽象状态类定义的接口,封装了该状态下的具体行为。比如:
-
待机状态下,“投币”操作会触发状态切换;
-
投币状态下,“选商品”操作会判断余额和库存,决定是否出货。
这里有个关键:具体状态类可以决定什么时候切换到其他状态,这个切换逻辑被封装在状态类内部,而不是环境类里。
2.2 状态模式的核心思想:封装状态,委托行为
把前面的角色串起来,状态模式的工作流程可以总结为四步:
-
环境类初始化时,设置一个默认的初始状态(比如自动售货机默认是待机状态);
-
客户端调用环境类的操作接口(比如调用投币方法);
-
环境类将该操作委托给当前持有的具体状态类的对应方法;
-
具体状态类执行该方法的逻辑,如果需要切换状态,就通知环境类更新当前状态的引用。
用一张流程图来展示这个过程,会更直观(以“投币”操作为例):

通过这种方式,状态的变化和状态对应的行为被彻底封装起来,环境类不再需要用switch-case判断状态,代码结构瞬间清晰。
三、C++实战:用状态模式重构自动售货机

理论讲完了,咱们马上用C++把自动售货机的代码重构一遍。按照状态模式的三个角色,咱们一步步来写,每一步都加详细注释,确保你能跟着复现。
3.1 第一步:定义抽象状态类
抽象状态类需要定义所有具体状态都要实现的行为接口。自动售货机的核心操作有投币、选商品、取零钱、补充商品,所以咱们在抽象类里定义这四个纯虚函数。
注意:抽象状态类需要持有环境类的引用,因为状态切换时需要通知环境类更新状态。这里用前向声明解决循环依赖问题。
#include <iostream>
#include <string>
using namespace std;
// 前向声明环境类,解决循环依赖
class VendingMachine;
// 抽象状态类:定义所有状态的行为接口
class VendingMachineState {
protected:
// 持有环境类的引用,用于状态切换
VendingMachine& machine;
public:
// 构造函数:传入环境类对象
VendingMachineState(VendingMachine& vm) : machine(vm) {}
// 纯虚函数:投币操作
virtual void insertCoin(int money) = 0;
// 纯虚函数:选择商品操作
virtual void selectProduct() = 0;
// 纯虚函数:取走零钱操作
virtual void takeChange() = 0;
// 纯虚函数:补充商品操作
virtual void replenishProduct() = 0;
// 虚析构函数:确保子类析构正常
virtual ~VendingMachineState() {}
};
3.2 第二步:定义环境类(自动售货机)
环境类需要持有当前状态的指针,提供操作接口,并暴露状态切换的方法(setState)给具体状态类调用。同时,环境类要保存售货机的核心数据(余额、商品价格、库存),这些数据是状态类执行逻辑的基础。
// 环境类:自动售货机
class VendingMachine {
private:
// 声明具体状态类为友元,使其能访问setState和内部数据
friend class StandbyState;
friend class CoinInsertedState;
friend class OutOfStockState;
friend class GiveChangeState;
// 抽象状态指针,持有当前状态
VendingMachineState* currentState;
// 售货机核心数据
int balance; // 用户余额
int productPrice; // 商品单价
int stock; // 商品库存(比之前的bool更真实)
// 状态对象(用单例模式,避免频繁创建销毁)
static VendingMachineState* standbyState;
static VendingMachineState* coinInsertedState;
static VendingMachineState* outOfStockState;
static VendingMachineState* giveChangeState;
// 状态切换方法:由具体状态类调用
void setState(VendingMachineState* newState) {
currentState = newState;
cout << "状态切换完成,当前状态:" << typeid(*currentState).name() << endl;
}
public:
// 构造函数:初始化数据和初始状态
VendingMachine(int price = 3, int initialStock = 10) {
productPrice = price;
stock = initialStock;
balance = 0;
// 初始化状态对象(单例)
if (standbyState == nullptr) {
standbyState = new StandbyState(*this);
coinInsertedState = new CoinInsertedState(*this);
outOfStockState = new OutOfStockState(*this);
giveChangeState = new GiveChangeState(*this);
}
// 初始状态为待机
currentState = standbyState;
cout << "自动售货机初始化完成,初始状态:待机状态" << endl;
}
// 析构函数:释放状态对象
~VendingMachine() {
delete standbyState;
delete coinInsertedState;
delete outOfStockState;
delete giveChangeState;
standbyState = nullptr;
coinInsertedState = nullptr;
outOfStockState = nullptr;
giveChangeState = nullptr;
}
// 对外接口:投币
void insertCoin(int money) {
if (money <= 0) {
cout << "请投入有效的钱币" << endl;
return;
}
currentState->insertCoin(money); // 委托给当前状态
}
// 对外接口:选择商品
void selectProduct() {
currentState->selectProduct(); // 委托给当前状态
}
// 对外接口:取走零钱
void takeChange() {
currentState->takeChange(); // 委托给当前状态
}
// 对外接口:补充商品
void replenishProduct(int num) {
if (num <= 0) {
cout << "请输入有效的补充数量" << endl;
return;
}
currentState->replenishProduct(); // 委托给当前状态
stock += num;
cout << "商品补充完成,当前库存:" << stock << endl;
}
// 获取余额(给状态类调用)
int getBalance() const { return balance; }
// 设置余额(给状态类调用)
void setBalance(int b) { balance = b; }
// 获取商品价格(给状态类调用)
int getProductPrice() const { return productPrice; }
// 获取库存(给状态类调用)
int getStock() const { return stock; }
// 减少库存(给状态类调用,出货时用)
void decreaseStock() { if (stock > 0) stock--; }
};
// 初始化静态状态指针(类外初始化)
VendingMachineState* VendingMachine::standbyState = nullptr;
VendingMachineState* VendingMachine::coinInsertedState = nullptr;
VendingMachineState* VendingMachine::outOfStockState = nullptr;
VendingMachineState* VendingMachine::giveChangeState = nullptr;
这里有两个关键点需要说明:
-
单例模式的使用:状态对象(比如待机状态)只需要一个实例,不需要每次切换状态都创建新对象,所以用静态指针实现单例,减少内存开销。
-
友元类的声明:具体状态类需要访问环境类的setState方法和内部数据(比如余额、库存),所以将具体状态类声明为友元,既保证了数据访问的安全性,又避免了暴露过多的public接口。
3.3 第三步:实现具体状态类
每个具体状态类都继承自抽象状态类,实现对应的行为接口。咱们按照状态的逻辑,逐个实现待机、投币、缺货、找零这四个状态类。
1. 待机状态类(StandbyState)
待机状态是初始状态,核心逻辑:可以投币,不能选商品(需先投币),不能取零钱(无余额),补充商品后保持待机。
// 具体状态类:待机状态
class StandbyState : public VendingMachineState {
public:
StandbyState(VendingMachine& vm) : VendingMachineState(vm) {}
// 投币操作:待机状态下投币,更新余额并切换到投币状态
void insertCoin(int money) override {
int currentBalance = machine.getBalance();
machine.setBalance(currentBalance + money);
cout << "投币成功,投入金额:" << money << ",当前余额:" << machine.getBalance() << endl;
// 切换到投币状态
machine.setState(&machine.coinInsertedState);
}
// 选择商品操作:待机状态下不能选商品,提示先投币
void selectProduct() override {
cout << "请先投币再选择商品" << endl;
}
// 取走零钱操作:待机状态下无零钱,提示用户
void takeChange() override {
cout << "当前无零钱可取,请先投币并完成购买流程" << endl;
}
// 补充商品操作:待机状态下补充商品,保持待机状态
void replenishProduct() override {
cout << "待机状态下补充商品,无需切换状态" << endl;
}
};
2. 投币状态类(CoinInsertedState)
投币状态是核心状态,逻辑最复杂:可以追加投币、可以选商品(判断库存和余额)、不能取零钱(需先完成购买或取消)、补充商品后保持投币状态。
// 具体状态类:投币状态
class CoinInsertedState : public VendingMachineState {
public:
CoinInsertedState(VendingMachine& vm) : VendingMachineState(vm) {}
// 投币操作:投币状态下可追加投币,只更新余额不切换状态
void insertCoin(int money) override {
int currentBalance = machine.getBalance();
machine.setBalance(currentBalance + money);
cout << "追加投币成功,投入金额:" << money << ",当前余额:" << machine.getBalance() << endl;
}
// 选择商品操作:投币状态下的核心逻辑,判断库存和余额
void selectProduct() override {
// 1. 检查库存
if (machine.getStock() <= 0) {
cout << "商品已缺货,即将为您全额退币" << endl;
// 退币:余额清零
machine.setBalance(0);
// 切换到待机状态
machine.setState(&machine.standbyState);
return;
}
// 2. 检查余额
int balance = machine.getBalance();
int price = machine.getProductPrice();
if (balance < price) {
cout << "余额不足,商品单价:" << price << ",当前余额:" << balance << ",请追加投币" << endl;
return;
}
// 3. 余额充足且有库存,执行出货逻辑
cout << "商品选择成功,正在出货..." << endl;
// 减少库存
machine.decreaseStock();
// 更新余额(减去商品价格)
machine.setBalance(balance - price);
cout << "商品已出货,请取走。剩余余额:" << machine.getBalance() << ",当前库存:" << machine.getStock() << endl;
// 4. 根据剩余余额决定下一个状态
if (machine.getBalance() > 0) {
// 有余额,切换到找零状态
machine.setState(&machine.giveChangeState);
} else {
// 无余额,切换到待机状态
machine.setState(&machine.standbyState);
}
}
// 取走零钱操作:投币状态下不能直接取零钱,提示完成购买
void takeChange() override {
cout << "请先选择商品或取消购买(可联系管理员)" << endl;
}
// 补充商品操作:投币状态下补充商品,保持投币状态
void replenishProduct() override {
cout << "投币状态下补充商品,无需切换状态" << endl;
}
};
3. 缺货状态类(OutOfStockState)
缺货状态的逻辑:不能投币(投了也买不了),不能选商品(没货),不能取零钱,补充商品后切换到待机状态。
// 具体状态类:缺货状态
class OutOfStockState : public VendingMachineState {
public:
OutOfStockState(VendingMachine& vm) : VendingMachineState(vm) {}
// 投币操作:缺货状态下禁止投币,提示用户
void insertCoin(int money) override {
cout << "抱歉,商品已缺货,无法投币,请选择其他售货机" << endl;
}
// 选择商品操作:缺货状态下禁止选商品
void selectProduct() override {
cout << "商品已缺货,无法完成购买" << endl;
}
// 取走零钱操作:缺货状态下无零钱可取
void takeChange() override {
cout << "当前无零钱可取" << endl;
}
// 补充商品操作:缺货状态下补充商品,切换到待机状态
void replenishProduct() override {
cout << "缺货状态下补充商品,状态将切换为待机" << endl;
machine.setState(&machine.standbyState);
}
};
4. 找零状态类(GiveChangeState)
找零状态的逻辑:不能投币(先取零钱),不能选商品(先取零钱),取走零钱后切换到待机状态,补充商品不影响找零状态。
// 具体状态类:找零状态
class GiveChangeState : public VendingMachineState {
public:
GiveChangeState(VendingMachine& vm) : VendingMachineState(vm) {}
// 投币操作:找零状态下禁止投币,提示先取零钱
void insertCoin(int money) override {
cout << "请先取走当前零钱,再进行投币操作" << endl;
}
// 选择商品操作:找零状态下禁止选商品,提示先取零钱
void selectProduct() override {
cout << "请先取走当前零钱,再选择商品" << endl;
}
// 取走零钱操作:找零状态下的核心逻辑,取走零钱后切换到待机
void takeChange() override {
int change = machine.getBalance();
if (change <= 0) {
cout << "无零钱可取,状态将切换为待机" << endl;
machine.setState(&machine.standbyState);
return;
}
cout << "请取走零钱:" << change << " 元" << endl;
// 零钱取走后余额清零
machine.setBalance(0);
// 切换到待机状态
machine.setState(&machine.standbyState);
}
// 补充商品操作:找零状态下补充商品,保持找零状态
void replenishProduct() override {
cout << "找零状态下补充商品,无需切换状态,请取走零钱后再购买" << endl;
}
};
3.4 第四步:测试状态模式的效果
咱们设计一个完整的测试场景,模拟用户的真实操作流程,看看状态模式是否能正常工作:
// 测试代码:模拟完整的用户操作流程
int main() {
// 创建自动售货机:商品单价3元,初始库存5个
VendingMachine vm(3, 5);
cout << "------------------------" << endl;
// 场景1:未投币选商品
vm.selectProduct();
cout << "------------------------" << endl;
// 场景2:投币2元(不足),追加投币2元(共4元)
vm.insertCoin(2);
vm.insertCoin(2);
cout << "------------------------" << endl;
// 场景3:选商品(余额4元,单价3元,剩余1元)
vm.selectProduct();
cout << "------------------------" << endl;
// 场景4:找零状态下投币(应该被拒绝)
vm.insertCoin(5);
cout << "------------------------" << endl;
// 场景5:取走零钱(1元)
vm.takeChange();
cout << "------------------------" << endl;
// 场景6:连续购买,直到缺货
vm.insertCoin(3);
vm.selectProduct(); // 库存4
vm.insertCoin(3);
vm.selectProduct(); // 库存3
vm.insertCoin(3);
vm.selectProduct(); // 库存2
vm.insertCoin(3);
vm.selectProduct(); // 库存1
vm.insertCoin(3);
vm.selectProduct(); // 库存0(缺货)
cout << "------------------------" << endl;
// 场景7:缺货状态下投币(被拒绝)
vm.insertCoin(3);
cout << "------------------------" << endl;
// 场景8:补充商品10个,恢复正常
vm.replenishProduct(10);
vm.insertCoin(3);
vm.selectProduct();
cout << "------------------------" << endl;
return 0;
}
3.5 测试结果与代码分析
运行上述代码,会输出以下结果(关键部分已标注):
自动售货机初始化完成,初始状态:待机状态
------------------------
请先投币再选择商品
------------------------
投币成功,投入金额:2,当前余额:2
状态切换完成,当前状态:class CoinInsertedState
追加投币成功,投入金额:2,当前余额:4
------------------------
商品选择成功,正在出货...
商品已出货,请取走。剩余余额:1,当前库存:4
状态切换完成,当前状态:class GiveChangeState
------------------------
请先取走当前零钱,再进行投币操作
------------------------
请取走零钱:1 元
状态切换完成,当前状态:class StandbyState
------------------------
投币成功,投入金额:3,当前余额:3
状态切换完成,当前状态:class CoinInsertedState
商品选择成功,正在出货...
商品已出货,请取走。剩余余额:0,当前库存:4
状态切换完成,当前状态:class StandbyState
// ... 中间省略重复的购买流程 ...
商品选择成功,正在出货...
商品已出货,请取走。剩余余额:0,当前库存:0
状态切换完成,当前状态:class StandbyState
------------------------
抱歉,商品已缺货,无法投币,请选择其他售货机
------------------------
缺货状态下补充商品,状态将切换为待机
商品补充完成,当前库存:10
投币成功,投入金额:3,当前余额:3
状态切换完成,当前状态:class CoinInsertedState
商品选择成功,正在出货...
商品已出货,请取走。剩余余额:0,当前库存:9
状态切换完成,当前状态:class StandbyState
------------------------
从测试结果可以看出,状态模式完美实现了自动售货机的所有逻辑,而且相比之前的switch-case版本,有三个显著优势:
-
可读性极强:每个状态的逻辑都封装在独立的类里,想知道“投币状态下选商品会怎样”,直接看CoinInsertedState的selectProduct方法就行,不用翻遍整个类。
-
可扩展性好:如果要新增“维护状态”,只需要新增一个MaintenanceState类,实现抽象接口,再在环境类里添加对应的状态对象,完全不用修改现有代码,符合开闭原则。
-
可维护性高:如果“投币逻辑”出问题,只需要修改CoinInsertedState和StandbyState的insertCoin方法,不会影响其他状态的逻辑,排查和修改都很高效。
四、状态模式的进阶技巧:避免踩坑+实际应用场景

学会了基础实现,咱们还要掌握状态模式的进阶技巧,避免实际开发中踩坑,同时明确它的适用场景,不要滥用设计模式。
4.1 状态模式的常见“坑”及避坑指南
很多人用状态模式时,会因为理解不深导致代码反而更复杂,这几个坑一定要避开:
坑1:状态过少时滥用状态模式
如果一个对象只有2-3个状态,且状态逻辑简单(比如“开关状态”:开/关),用if-else反而比状态模式更简洁。状态模式的优势在状态数量多(≥4个)、逻辑复杂时才会体现。
避坑指南:先评估状态数量和逻辑复杂度,再决定是否使用。简单场景优先用条件判断,复杂场景再用状态模式。
坑2:状态切换逻辑分散在环境类中
有些开发者会把状态切换的逻辑写在环境类里,而不是具体状态类里,导致环境类又变成了“大杂烩”。比如在环境类的selectProduct方法里判断“如果余额充足就切换到找零状态”,这违背了状态模式的核心思想。
避坑指南:状态切换的逻辑必须封装在具体状态类中,环境类只负责“持有状态”和“提供数据”,不参与状态判断。
坑3:状态类之间相互依赖
比如A状态切换到B状态时,A状态类直接创建B状态的对象,这会导致状态类之间耦合严重。比如:
// 错误示例:A状态直接依赖B状态
void AState::doAction() {
// 直接创建B状态对象,耦合严重
machine.setState(new BState(machine));
}
避坑指南:用环境类的静态状态对象(单例),状态类通过环境类获取其他状态的引用,避免直接创建。就像咱们前面的实现,通过machine.coinInsertedState获取投币状态对象。
坑4:忽略状态的持久化
在实际项目中,很多对象的状态需要持久化(比如订单状态,要存在数据库里)。如果只用内存中的状态对象,程序重启后状态就丢失了。
避坑指南:在环境类中添加状态的序列化和反序列化方法,将状态信息(比如“待机状态”对应枚举值0)存储到数据库或文件中,程序启动时从存储中恢复状态。
4.2 状态模式的实际应用场景
状态模式在实际开发中应用非常广泛,只要遇到“对象状态驱动行为”的场景,都可以考虑使用。以下是几个典型场景:
1. 订单状态管理(电商系统)
电商订单的状态变化非常复杂:待支付→已支付→已发货→已完成→已取消,每个状态下的行为都不同(比如待支付状态可以取消订单,已发货状态不能取消)。用状态模式可以清晰地管理每个状态的逻辑,新增“退款中”状态时也很方便。
2. 工作流引擎(OA系统)
OA系统中的审批流程(比如请假审批):待审批→部门经理审批→总经理审批→已通过→已驳回。每个审批节点就是一个状态,不同状态下的操作人、操作权限都不同,状态模式可以让审批流程的逻辑更清晰。
3. 游戏角色状态(游戏开发)
游戏角色的状态:正常→受伤→中毒→死亡,每个状态下的属性(比如移动速度、攻击力)都不同。用状态模式可以将每个状态的属性变化和行为封装起来,切换状态时直接切换对应的状态类。
4. 设备状态管理(嵌入式开发)
嵌入式设备(比如打印机)的状态:待机→打印中→缺纸→卡纸→故障,每个状态下的设备响应逻辑不同(比如缺纸状态下不能执行打印操作),状态模式可以让设备的控制逻辑更可靠。
4.3 状态模式与策略模式的区别
很多人会把状态模式和策略模式搞混,因为它们的类图结构非常相似,都有“环境类依赖抽象类,具体类实现抽象接口”的结构。但它们的核心思想完全不同:
| 对比维度 | 状态模式 | 策略模式 |
|---|---|---|
| 核心目的 | 处理对象的状态变化,状态决定行为 | 封装不同的算法,让算法可替换 |
| 状态/策略的关系 | 状态之间有固定的切换规则(比如待机→投币) | 策略之间相互独立,无固定切换规则 |
| 环境类与抽象类的交互 | 状态类会主动通知环境类切换状态 | 策略类不会影响环境类,由客户端决定切换策略 |
| 典型场景 | 订单状态、设备状态 | 排序算法、支付方式选择 |
简单总结:状态模式是“内部状态驱动行为变化”,策略模式是“外部策略决定算法变化”。比如:自动售货机的状态变化是内部逻辑驱动的(投币后自动切换状态),而支付方式选择是外部客户端驱动的(用户手动选微信或支付宝)。
五、总结:状态模式的核心价值

看到这里,相信你已经对状态模式有了全面的理解。咱们最后再总结一下它的核心价值,帮你记住什么时候该用、为什么要用:
-
解耦状态与行为:把每个状态的行为从环境类中抽离出来,避免了用条件判断串联所有逻辑,让代码结构更清晰。
-
符合开闭原则:新增状态时,只需要新增具体状态类,不用修改现有代码,降低了维护成本。
-
提高代码可读性:每个状态的逻辑都封装在独立的类中,代码的职责更明确,新人接手也能快速理解。
-
简化状态切换逻辑:状态切换的规则由具体状态类控制,避免了环境类成为“状态判断的大杂烩”。
最后提醒一句:设计模式不是“银弹”,不要为了用模式而用模式。只有当你的代码中出现“大量条件判断控制状态”的问题时,状态模式才是最优解。就像咱们开头的自动售货机,用switch-case写出来的代码臃肿不堪,而用状态模式重构后,代码瞬间变得优雅、可扩展。
如果你在实际项目中遇到了状态管理的难题,不妨试试状态模式,相信它会让你的代码从“臃肿的面条”变成“优雅的舞蹈”。如果这篇文章对你有帮助,欢迎点赞、收藏,也可以在评论区分享你用状态模式解决的实际问题~

1515

被折叠的 条评论
为什么被折叠?



