行为型设计模式——状态模式

文章目录

状态模式

场景

在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在同的状态下也将具有不同的行为 。如果使用复杂的条件判断语句(如if或switch)来进行状态的判断和换操作,这会导致代码的可维护性和灵活性下降,特别是出现新状态的时候代码的扩展性很差,客户端码也需要进行修改,违反开闭原则。为了更好地对这些具有多种状态的对象进行设计,我们可以使用一被称之为状态模式(State Pattern) 的设计模式。

  • 人在幼年、童年、少年、中年、老年各个使其的形态都是不一样的
  • 工作期间,上午、中午、下午、傍晚、深夜的工作状态也不一样
  • 人的心情不同时,会有喜、怒、哀、乐
  • 手机在待机、通话、充电、游戏时的状态也不一样
  • 文章的发表会有草稿、审阅、发布状态

⚠️状态模式和策略模式比较类似,策略模式中的各个策略是独立的不关联的,但是状态模式下的对象的各种状态可以是独立的也可以是相互依赖的 ,比如上面关于文章的发布的例子:

  • 普通用户的文章草稿发表之后被审阅,审阅失败重新变成草稿
  • 管理用户的文章操作发布成功变成已发表状态, 发布失败重新变成草稿

> 状态模式就是在一个类的内部会有多种状态的变化,因为状态变化从而导致其行为的改变,在类的外部看上去这个类就像是自身发生了改变一样。 >

结构

在状态模式结构图中包含如下几个角色:

  • Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
  • State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
  • Concrete State(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

在状态模式的使用过程中,一个对象的状态之间还可以进行相互转换,通常有两种实现状态转换的方式:

cpp 复制代码
public:
void changeState() {
    / 判断属性值,根据属性值进行状态转换
    if (value = 0)
    {
        this->setState(new ConcreteStateA());
    }
    else if (value = 1)
    {
        this->setState(new ConcreteStateB());
    }
}
cpp 复制代码
public:
void changeState(Context* ctx) {
    / 根据环境对象中的属性值进行状态转换
    if (ctx->getValue() = 1)
    {
        ctx->setState(new ConcreteStateB());
    }
    else if (ctx->getValue() = 2)
    {
        ctx->setState(new ConcreteStateC());
    }
}

实现

我们可以看到状态模式下各个模式之间是可以有依赖关系的,这一点和策略模式是有区别的,策略模式下各个策略都是独立的,当前策略不知道有其它策略的存在。
cpp 复制代码
// State.h
// 抽象状态
class Sanji;
class AbstractState
{
public:
    virtual void working(Sanji* sanji) = 0;
    virtual ~AbstractState() {}
};
cpp 复制代码
// 上午状态
class ForenoonState : public AbstractState
{
public:
    void working(Sanji* sanji) override;
};

// 中午状态
class NoonState : public AbstractState
{
public:
    void working(Sanji* sanji) override;
};

// 下午状态
class AfternoonState : public AbstractState
{
public:
    void working(Sanji* sanji) override;
};

// 晚上状态
class EveningState : public AbstractState
{
public:
    void working(Sanji* sanji) override;
};

#include <iostream>
#include "State.h"
#include "Sanji.h"
using namespace std;

void ForenoonState::working(Sanji* sanji)
{
    int time = sanji->getClock();
    if (time < 8)
    {
        cout << "当前时间<" << time << ">点, 准备早餐, 布鲁克得多喝点牛奶..." << endl;
    }
    else if (time > 8 && time < 11)
    {
        cout << "当前时间<" << time << ">点, 去船头钓鱼, 储备食材..." << endl;
    }
    else
    {
        sanji->setState(new NoonState);
        sanji->working();
    }
}

void NoonState::working(Sanji* sanji)
{
    int time = sanji->getClock();
    if (time < 13)
    {
        cout << "当前时间<" << time << ">点, 去厨房做午饭, 给路飞多做点肉..." << endl;
    }
    else
    {
        sanji->setState(new AfternoonState);
        sanji->working();
    }
}

void AfternoonState::working(Sanji* sanji)
{
    int time = sanji->getClock();
    if (time < 15)
    {
        cout << "当前时间<" << time << ">点, 准备下午茶, 给罗宾和娜美制作爱心甜点..." << endl;
    }
    else if (time > 15 && time < 18)
    {
        cout << "当前时间<" << time << ">点, 和乔巴去船尾钓鱼, 储备食材..." << endl;
    }
    else
    {
        sanji->setState(new EveningState);
        sanji->working();
    }
}

void EveningState::working(Sanji* sanji)
{
    int time = sanji->getClock();
    if (time < 19)
    {
        cout << "当前时间<" << time << ">点, 去厨房做晚饭, 让索隆多喝点汤..." << endl;
    }
    else
    {
        cout << "当前时间<" << time << ">点, 今天过得很高兴, 累了睡觉了..." << endl;
    }
}
cpp 复制代码
// Sanji.h
#pragma once
#include "State.h"

class Sanji
{
public:
    Sanji()
    {
        m_state = new ForenoonState;
    }
    //工作函数,在不同的时间状态下,工作的内容也不同
    void working()
    {
        m_state->working(this);
    }
    //设置山治当前的状态
    void setState(AbstractState* state)
    {
        if (m_state != nullptr)
        {
            delete m_state;
        }
        m_state = state;
    }
    //设置当前的时间
    void setClock(int time)
    {
        m_clock = time;
    }
    //得到当前的时间
    int getClock()
    {
        return m_clock;
    }
    ~Sanji()
    {
        delete m_state;
    }
private:
    //通过这整形的时钟变量来描述一天中当前这个时刻的时间点
    int m_clock = 0;    // 时钟
    //通过这个状态指针来保存当前描述山治状态的对象
    AbstractState* m_state = nullptr;
};
cpp 复制代码
int main()
{
    Sanji* sanji = new Sanji;
    // 时间点
    vector<int> data{7, 10, 12, 14, 16, 18, 22};
    for (const auto& item : data)
    {
        sanji->setClock(item);
        sanji->working();
    }
    delete sanji;

    return 0;
} 

特点

在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,例如公文状态的转换、游戏中角色状态切换等。

主要优点

  • 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
  • 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

主要缺点

  • 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的

难度

  • 状态模式对"开闭原则"的支持并不太好
    • 增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;
    • 修改某个状态类的行为也需修改对应类的源代码。

适用环境

  • 如果对象需要根据当前自身状态进行不同的行为, 同时状态的数量非常多且与状态相关的代码会频繁变更或者类对象在改变自身行为时需要使用大量的条件语句时,可使用状态模式。
  • 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。
  • 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。
  • 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。
相关推荐
折哥的程序人生 · 物流技术专研12 小时前
Java 23 种设计模式:从踩坑到精通 —— 开篇及系列介绍
java·开发语言·后端·设计模式·面试·架构
蜡笔小马12 小时前
15.C++设计模式-观察者模式
c++·观察者模式·设计模式
ZC跨境爬虫12 小时前
跟着 MDN 学CSS day_17:(深入理解溢出机制与容器控制艺术)
前端·javascript·css·ui·交互
ZC跨境爬虫1 天前
跟着 MDN 学CSS day_14:(尺寸调整技能测试与实战解析)
前端·css·ui·html·tensorflow
ZC跨境爬虫1 天前
跟着 MDN 学CSS day_13 :(深入理解CSS中的元素尺寸调整)
前端·javascript·css·ui·html·tensorflow
贵慜_Derek1 天前
《从零实现 Agent 系统》连载 07|记忆系统:短期上下文 vs 长期外部记忆
人工智能·设计模式·架构
掌动智能1 天前
从“感知”到“认知”:RunnerAgent如何重塑UI自动化的稳定边界
人工智能·ui·自动化
老码观察1 天前
设计模式实战解读(一):单例模式——全局唯一实例的正确打开方式
单例模式·设计模式