Bootstrap

16.状态模式(State Pattern)

定义

状态模式(State Pattern) 是一种行为型设计模式,它允许对象在其内部状态改变时改变其行为。也就是说,状态模式通过封装对象的状态,使得对象在不同的状态下表现出不同的行为,状态的切换由状态对象来控制。

基本思想:

  • 状态对象:每个状态都由一个独立的类来表示,每个状态类实现相同的接口,并根据当前状态执行不同的操作。
  • 上下文对象:代表有状态变化的对象,它包含一个当前状态,并委托给当前状态类来处理具体的行为。
  • 状态的切换:对象的行为根据其当前状态来变化,状态可以动态地改变,因此对象的行为也可以在运行时发生变化。

场景

1.适用场景

  • 对象的行为依赖于状态:当一个对象的行为依赖于其内部状态,且状态之间的转换是频繁且明显时,可以使用状态模式。
  • 状态转换多次:如果系统中有多个状态且每个状态下的行为有所不同,状态模式能有效组织这些行为。
  • 封装复杂的状态转移:当一个对象的状态转换较为复杂或有多个条件时,使用状态模式可以将状态转换的逻辑从对象中抽离出来,使得代码更清晰。

2.常见应用场景:

  • 文件状态管理:文件可能有多个状态(如“打开”、“关闭”、“编辑”),根据不同状态,文件的操作也会不同。
  • UI组件状态切换:UI组件(如按钮)可以根据不同的状态(如“禁用”、“启用”、“加载中”)显示不同的外观或响应不同的用户操作。
  • 工作流管理:在多个阶段的工作流系统中,每个阶段有不同的操作和行为,根据当前工作流的状态来执行不同的操作。

类设计

状态模式通常包括以下几个角色:

  • State(状态接口):定义一个接口,表示当前对象在不同状态下的行为。
  • ConcreteState(具体状态类):实现 State 接口,封装与某个具体状态相关的行为。
  • Context(上下文类):维护一个 State 对象并在必要时进行状态切换。通过 State 对象来执行具体的行为。
  • Client(客户端):客户端通过 Context 类与系统交互,客户端可以改变 Context 的状态,从而触发不同的行为。

代码实现

我们模拟一个 投票系统。用户的投票状态可以是“未投票”、“已投票”。根据投票状态,用户可以执行不同的操作。我们使用状态模式来实现这个功能。

1. 定义状态接口

#include <iostream>
#include <string>
using namespace std;

// 状态接口
class VoteState {
public:
    virtual void vote() = 0;  // 定义投票方法
};

  • VoteState 是状态接口,定义了 vote() 方法,表示在不同状态下的投票行为。

2. 定义具体状态类

// 未投票状态
class NoVoteState : public VoteState {
public:
    void vote() override {
        cout << "You haven't voted yet. Please cast your vote." << endl;
    }
};

// 已投票状态
class VotedState : public VoteState {
public:
    void vote() override {
        cout << "You have already voted. You cannot vote again." << endl;
    }
};

// 已取消投票状态
class CanceledVoteState : public VoteState {
public:
    void vote() override {
        cout << "You have canceled your vote. You cannot vote now." << endl;
    }
};

  • 每个具体状态类(NoVoteState、VotedState、CanceledVoteState)都实现了 VoteState 接口的 vote() 方法,表示在不同状态下的具体投票行为。

3. 定义上下文类(Context)

class VotingContext {
private:
    VoteState* state;  // 当前的状态

public:
    VotingContext() : state(new NoVoteState()) {}  // 默认状态为未投票

    void setState(VoteState* newState) {
        state = newState;  // 设置新的状态
    }

    void vote() {
        state->vote();  // 委托给当前状态对象执行操作
    }

    ~VotingContext() {
        delete state;  // 释放状态对象
    }
};

  • VotingContext 类代表一个用户的投票上下文,管理当前的投票状态。它可以动态地切换状态。
  • vote() 方法委托给当前的状态对象执行投票操作。
  • setState() 方法用于设置当前的状态。

4. 客户端调用

int main() {
    VotingContext* context = new VotingContext();  // 默认状态:未投票

    // 客户端投票
    context->vote();  // 输出: "You haven't voted yet. Please cast your vote."
    
    // 切换到已投票状态
    context->setState(new VotedState());
    context->vote();  // 输出: "You have already voted. You cannot vote again."
    
    // 切换到已取消投票状态
    context->setState(new CanceledVoteState());
    context->vote();  // 输出: "You have canceled your vote. You cannot vote now."

    delete context;

    return 0;
}

5.输出结果

You haven't voted yet. Please cast your vote.
You have already voted. You cannot vote again.
You have canceled your vote. You cannot vote now.

  • 第一次调用 vote() 时,输出未投票状态的提示。
  • 第二次调用时,状态变为已投票,输出已投票的提示。
  • 第三次调用时,状态变为已取消投票,输出已取消投票的提示。

状态模式的优缺点

优点:

  • 简化对象的行为:将对象的行为封装到不同的状态对象中,避免了复杂的条件语句(如 if、switch)来处理不同状态下的行为。
  • 易于扩展:当需要增加新的状态时,只需要新增一个具体状态类,原有的代码不需要修改,符合开闭原则(Open/Closed Principle)。
  • 避免大量条件判断:状态模式通过将条件判断移到状态类中,简化了上下文类中的代码,使得代码更加清晰和可维护。

缺点:

  • 类的数量增多:每个状态都需要创建一个具体的状态类,可能导致类的数量增多。
  • 状态之间的转换较复杂时,维护可能变得困难:如果状态之间的转换逻辑非常复杂,状态模式可能会导致过多的状态类,从而增加维护的复杂度。

适用场景:

  • 状态依赖的行为:当一个对象的行为依赖于其内部状态,并且状态之间的转换是明显和频繁的。
  • 状态变化频繁:如果对象有多个状态,并且状态之间的切换比较复杂,使用状态模式可以使得代码更加清晰、易于管理。
  • UI组件的状态变化:在GUI中,一个组件可能有多个状态(如按钮的启用/禁用,加载/完成等),使用状态模式可以优雅地处理这些状态转换。
  • 工作流系统:当工作流系统中有多个阶段时,状态模式可以很好地将每个阶段的行为封装到不同的状态对象中,并根据当前阶段进行适当的操作。

编程相关的案例

1.文件处理状态:文件的不同状态(如“读取中”、“写入中”、“已关闭”等)需要不同的操作。
2.视频播放控制:视频播放器的状态(如“播放中”、“暂停”、“停止”等)需要根据状态提供不同的操作(如播放、暂停、停止按钮的显示状态和行为)。
3.网络请求状态:当发起网络请求时,可能存在多个状态(如“正在请求”、“请求成功”、“请求失败”),每个状态下的处理方式不同。

总结

状态模式通过将对象的行为和状态封装在独立的状态类中,简化了对象的状态管理,并使得代码更加可扩展。它特别适用于那些状态间转换复杂且频繁的场景,可以避免使用大量的条件语句来处理不同的状态,从而使代码更加简洁和易于维护。

;