状态模式实战:告别臃肿if-else!让你的代码优雅起舞

📕目录

前言

一、从自动售货机说起:为什么需要状态模式?

二、状态模式核心解析:3个角色+1个核心思想

2.1 状态模式的三个核心角色

1. 环境类(Context):状态的“持有者”和“切换器”

2. 抽象状态类(State):状态行为的“规范”

3. 具体状态类(Concrete State):状态行为的“实现者”

2.2 状态模式的核心思想:封装状态,委托行为

三、C++实战:用状态模式重构自动售货机

3.1 第一步:定义抽象状态类

3.2 第二步:定义环境类(自动售货机)

3.3 第三步:实现具体状态类

1. 待机状态类(StandbyState)

2. 投币状态类(CoinInsertedState)

3. 缺货状态类(OutOfStockState)

4. 找零状态类(GiveChangeState)

3.4 第四步:测试状态模式的效果

3.5 测试结果与代码分析

四、状态模式的进阶技巧:避免踩坑+实际应用场景

4.1 状态模式的常见“坑”及避坑指南

坑1:状态过少时滥用状态模式

坑2:状态切换逻辑分散在环境类中

坑3:状态类之间相互依赖

坑4:忽略状态的持久化

4.2 状态模式的实际应用场景

1. 订单状态管理(电商系统)

2. 工作流引擎(OA系统)

3. 游戏角色状态(游戏开发)

4. 设备状态管理(嵌入式开发)

4.3 状态模式与策略模式的区别

五、总结:状态模式的核心价值


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;
}

这段代码能跑起来,但如果你是维护者,看到这样的代码会不会头皮发麻?它的问题太明显了:

  1. 状态逻辑混乱:所有状态的判断都揉在switch-case里,投币、选商品、找零的逻辑相互交织,想改一个状态的行为都要翻遍所有相关方法。

  2. 违反开闭原则:如果要新增“维护状态”(此时不能投币、不能选商品),就要在每个switch-case里都加一个case分支,改动量大且容易出错。

  3. 可读性差:新人接手时,要理清每个状态下每个操作的逻辑,得逐行读代码,效率极低。

  4. 可维护性低:某个状态的逻辑出问题,比如投币后余额计算错误,可能要修改多个地方,排查成本高。

而状态模式就是为解决这类“对象状态驱动行为”的问题而生的。它的核心思路很简单:把每个状态的行为封装成独立的类,让对象的状态变化时,直接切换对应的状态类,而不是通过一堆条件判断。就像自动售货机,每个状态(待机、投币等)都像一个“功能模块”,切换状态就是换模块,不用动核心逻辑。

二、状态模式核心解析:3个角色+1个核心思想

在正式讲状态模式的定义前,咱们先明确它的核心组成。根据《设计模式:可复用面向对象软件的基础》中的定义,状态模式包含三个核心角色,这三个角色协同工作,实现“状态决定行为”的效果。

2.1 状态模式的三个核心角色

咱们结合自动售货机的例子,一个个拆解这三个角色,保证你一看就懂:

1. 环境类(Context):状态的“持有者”和“切换器”

环境类就是那个拥有状态的对象,比如咱们的自动售货机。它的核心职责有两个:

  • 持有一个当前状态的引用(比如“当前处于投币状态”);

  • 提供对外的操作接口(比如投币、选商品),并将这些操作委托给当前的状态类去处理;

  • 负责状态之间的切换(比如投币后从“待机状态”切换到“投币状态”)。

简单说,环境类是“门面”,对外提供统一的操作入口,对内则把具体工作交给状态类。

2. 抽象状态类(State):状态行为的“规范”

抽象状态类是所有具体状态的父类,它定义了每个状态应该具备的行为接口。比如自动售货机的所有状态,都需要处理“投币”“选商品”“取零钱”这几个操作,所以抽象状态类就要定义这几个纯虚函数,让具体状态类去实现。

这样做的好处是:环境类只需要依赖抽象状态类,不用关心具体是哪个状态,符合“依赖倒置原则”(依赖抽象,不依赖具体)。

3. 具体状态类(Concrete State):状态行为的“实现者”

每个具体状态类对应一个实际的状态,比如“待机状态类”“投币状态类”。它们实现抽象状态类定义的接口,封装了该状态下的具体行为。比如:

  • 待机状态下,“投币”操作会触发状态切换;

  • 投币状态下,“选商品”操作会判断余额和库存,决定是否出货。

这里有个关键:具体状态类可以决定什么时候切换到其他状态,这个切换逻辑被封装在状态类内部,而不是环境类里。

2.2 状态模式的核心思想:封装状态,委托行为

把前面的角色串起来,状态模式的工作流程可以总结为四步:

  1. 环境类初始化时,设置一个默认的初始状态(比如自动售货机默认是待机状态);

  2. 客户端调用环境类的操作接口(比如调用投币方法);

  3. 环境类将该操作委托给当前持有的具体状态类的对应方法;

  4. 具体状态类执行该方法的逻辑,如果需要切换状态,就通知环境类更新当前状态的引用。

用一张流程图来展示这个过程,会更直观(以“投币”操作为例):

通过这种方式,状态的变化和状态对应的行为被彻底封装起来,环境类不再需要用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;

这里有两个关键点需要说明:

  1. 单例模式的使用:状态对象(比如待机状态)只需要一个实例,不需要每次切换状态都创建新对象,所以用静态指针实现单例,减少内存开销。

  2. 友元类的声明:具体状态类需要访问环境类的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版本,有三个显著优势:

  1. 可读性极强:每个状态的逻辑都封装在独立的类里,想知道“投币状态下选商品会怎样”,直接看CoinInsertedState的selectProduct方法就行,不用翻遍整个类。

  2. 可扩展性好:如果要新增“维护状态”,只需要新增一个MaintenanceState类,实现抽象接口,再在环境类里添加对应的状态对象,完全不用修改现有代码,符合开闭原则。

  3. 可维护性高:如果“投币逻辑”出问题,只需要修改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 状态模式与策略模式的区别

很多人会把状态模式和策略模式搞混,因为它们的类图结构非常相似,都有“环境类依赖抽象类,具体类实现抽象接口”的结构。但它们的核心思想完全不同:

对比维度

状态模式

策略模式

核心目的

处理对象的状态变化,状态决定行为

封装不同的算法,让算法可替换

状态/策略的关系

状态之间有固定的切换规则(比如待机→投币)

策略之间相互独立,无固定切换规则

环境类与抽象类的交互

状态类会主动通知环境类切换状态

策略类不会影响环境类,由客户端决定切换策略

典型场景

订单状态、设备状态

排序算法、支付方式选择

简单总结:状态模式是“内部状态驱动行为变化”,策略模式是“外部策略决定算法变化”。比如:自动售货机的状态变化是内部逻辑驱动的(投币后自动切换状态),而支付方式选择是外部客户端驱动的(用户手动选微信或支付宝)。

五、总结:状态模式的核心价值

看到这里,相信你已经对状态模式有了全面的理解。咱们最后再总结一下它的核心价值,帮你记住什么时候该用、为什么要用:

  1. 解耦状态与行为:把每个状态的行为从环境类中抽离出来,避免了用条件判断串联所有逻辑,让代码结构更清晰。

  2. 符合开闭原则:新增状态时,只需要新增具体状态类,不用修改现有代码,降低了维护成本。

  3. 提高代码可读性:每个状态的逻辑都封装在独立的类中,代码的职责更明确,新人接手也能快速理解。

  4. 简化状态切换逻辑:状态切换的规则由具体状态类控制,避免了环境类成为“状态判断的大杂烩”。

最后提醒一句:设计模式不是“银弹”,不要为了用模式而用模式。只有当你的代码中出现“大量条件判断控制状态”的问题时,状态模式才是最优解。就像咱们开头的自动售货机,用switch-case写出来的代码臃肿不堪,而用状态模式重构后,代码瞬间变得优雅、可扩展。

如果你在实际项目中遇到了状态管理的难题,不妨试试状态模式,相信它会让你的代码从“臃肿的面条”变成“优雅的舞蹈”。如果这篇文章对你有帮助,欢迎点赞、收藏,也可以在评论区分享你用状态模式解决的实际问题~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值