设计模式之状态模式

简单来说,**状态模式(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。

一句话总结:

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

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

相关推荐
双手插兜-装高手6 分钟前
C++设计模式
c++·设计模式
别催小唐敲代码43 分钟前
前后端交互原理与架构全解
架构·状态模式·前后端
小箌11 小时前
springboot_03
spring boot·后端·状态模式
Detachym15 小时前
InsightFlow 服务配置优化与部署实践
java·spring boot·tomcat·maven·状态模式·jar
sevenlin19 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
逆境不可逃19 小时前
【从零入门23种设计模式23】行为型之模板模式
java·开发语言·算法·设计模式·职场和发展·模板模式
Aaron_dw1 天前
QT软件开发设计模式-模板方法模式
qt·设计模式·模板方法模式
Aaron_dw1 天前
QT软件开发设计模式-观察者模式
qt·观察者模式·设计模式
Chasing__Dreams1 天前
python--设计模式--13.1--结构性--享元模式
python·设计模式·享元模式
彭于晏Yan1 天前
Spring Boot中适配器模式的实现方式
spring boot·设计模式·适配器模式