设计模式之状态模式

简单来说,**状态模式(State Pattern)**就是为了解决代码中那堆令人头疼的 if-elseswitch-case。当一个对象的行为取决于它的"状态"时,我们干脆把每种状态都封装成一个独立的类。

场景设定:自动贩卖机

想象一个简单的自动贩卖机,它有两个状态:

  1. NoMoney(待投币)

  2. 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());
    }
}

但当你的需求变成:

  1. 投币成功 -> 2. 选择商品 -> 3. 检查库存 -> 4. 出货中 -> 5. 故障处理 -> 6. 维护模式...

这时候,状态模式 就像是把一团乱麻拆成了 一格一格的抽屉

  • 以后想改"故障处理"的逻辑?直接拉开那个抽屉改就行。

  • 其他抽屉(投币、出货)动都不用动,绝对不会被你改出 Bug。

一句话总结:

  • 硬编码 是把逻辑写在动作里(投币时判断状态)。

  • 状态模式 是把逻辑写在状态里(在没钱状态下处理投币)。

相关推荐
电子科技圈2 小时前
XMOS推动智能音频等媒体处理技术从嵌入式系统转向全新边缘计算
人工智能·mcu·物联网·设计模式·音视频·边缘计算·iot
徐先生 @_@|||12 小时前
安装依赖三方exe/msi的软件设计模式
设计模式
小王不爱笑1321 天前
LangChain4j 项目实战--4:硅谷小智(实现流式输出)
状态模式
希望_睿智1 天前
实战设计模式之访问者模式
c++·设计模式·架构
茶本无香1 天前
设计模式之十六:状态模式(State Pattern)详解 -优雅地管理对象状态,告别繁琐的条件判断
java·设计模式·状态模式
驴儿响叮当20101 天前
设计模式之备忘录模式
设计模式·备忘录模式
驴儿响叮当20101 天前
设计模式之迭代器模式
设计模式·迭代器模式
qq_401700411 天前
嵌入式C语言设计模式
c语言·开发语言·设计模式
SuperEugene1 天前
常见设计模式在 JS 里的轻量用法:单例、发布订阅、策略
前端·javascript·设计模式·面试