当前位置: 首页 > news >正文

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

文章目录

    • 状态模式
      • 结构
      • 实现
      • 特点

状态模式

场景

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

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

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

  • 普通用户的文章草稿发表之后被审阅,审阅失败重新变成草稿
  • 管理用户的文章操作发布成功变成已发表状态, 发布失败重新变成草稿
> 状态模式就是在一个类的内部会有多种状态的变化,因为状态变化从而导致其行为的改变,在类的外部看上去这个类就像是自身发生了改变一样。 >

结构

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

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

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

public:voidchangeState(){/判断属性值,根据属性值进行状态转换if(value=0){this->setState(newConcreteStateA());}elseif(value=1){this->setState(newConcreteStateB());}}
public:voidchangeState(Context*ctx){/根据环境对象中的属性值进行状态转换if(ctx->getValue()=1){ctx->setState(newConcreteStateB());}elseif(ctx->getValue()=2){ctx->setState(newConcreteStateC());}}

实现

// State.h// 抽象状态classSanji;classAbstractState{public:virtualvoidworking(Sanji*sanji)=0;virtual~AbstractState(){}};
// 上午状态classForenoonState:publicAbstractState{public:voidworking(Sanji*sanji)override;};// 中午状态classNoonState:publicAbstractState{public:voidworking(Sanji*sanji)override;};// 下午状态classAfternoonState:publicAbstractState{public:voidworking(Sanji*sanji)override;};// 晚上状态classEveningState:publicAbstractState{public:voidworking(Sanji*sanji)override;};#include<iostream>#include"State.h"#include"Sanji.h"usingnamespacestd;voidForenoonState::working(Sanji*sanji){inttime=sanji->getClock();if(time<8){cout<<"当前时间<"<<time<<">点, 准备早餐, 布鲁克得多喝点牛奶..."<<endl;}elseif(time>8&&time<11){cout<<"当前时间<"<<time<<">点, 去船头钓鱼, 储备食材..."<<endl;}else{sanji->setState(newNoonState);sanji->working();}}voidNoonState::working(Sanji*sanji){inttime=sanji->getClock();if(time<13){cout<<"当前时间<"<<time<<">点, 去厨房做午饭, 给路飞多做点肉..."<<endl;}else{sanji->setState(newAfternoonState);sanji->working();}}voidAfternoonState::working(Sanji*sanji){inttime=sanji->getClock();if(time<15){cout<<"当前时间<"<<time<<">点, 准备下午茶, 给罗宾和娜美制作爱心甜点..."<<endl;}elseif(time>15&&time<18){cout<<"当前时间<"<<time<<">点, 和乔巴去船尾钓鱼, 储备食材..."<<endl;}else{sanji->setState(newEveningState);sanji->working();}}voidEveningState::working(Sanji*sanji){inttime=sanji->getClock();if(time<19){cout<<"当前时间<"<<time<<">点, 去厨房做晚饭, 让索隆多喝点汤..."<<endl;}else{cout<<"当前时间<"<<time<<">点, 今天过得很高兴, 累了睡觉了..."<<endl;}}
// Sanji.h#pragmaonce#include"State.h"classSanji{public:Sanji(){m_state=newForenoonState;}//工作函数,在不同的时间状态下,工作的内容也不同voidworking(){m_state->working(this);}//设置山治当前的状态voidsetState(AbstractState*state){if(m_state!=nullptr){deletem_state;}m_state=state;}//设置当前的时间voidsetClock(inttime){m_clock=time;}//得到当前的时间intgetClock(){returnm_clock;}~Sanji(){deletem_state;}private://通过这整形的时钟变量来描述一天中当前这个时刻的时间点intm_clock=0;// 时钟//通过这个状态指针来保存当前描述山治状态的对象AbstractState*m_state=nullptr;};
intmain(){Sanji*sanji=newSanji;// 时间点vector<int>data{7,10,12,14,16,18,22};for(constauto&item:data){sanji->setClock(item);sanji->working();}deletesanji;return0;}

特点

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

主要优点

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

主要缺点

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

难度

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

适用环境

  • 如果对象需要根据当前自身状态进行不同的行为, 同时状态的数量非常多且与状态相关的代码会频繁变更或者类对象在改变自身行为时需要使用大量的条件语句时,可使用状态模式。
  • 如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。
  • 如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。
  • 当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。
http://www.cnnetsun.cn/news/2567392.html

相关文章:

  • 【Android】AI视频剪辑-Ai剪辑视频 免费无广告
  • STM32和FPGA怎么‘分工’才高效?一份给多轴运动控制新手的软硬件协同设计指南
  • AI语音合成性价比怎么选?3大维度+5个关键指标,帮你省下60%预算
  • ARM活动监视器(AMU)架构与性能监控实践
  • 三路音调控制电路设计:基于Baxandall架构的独立中频调节方案
  • 基于LM22678的树莓派硬盘专用电源设计:解决供电不稳与电流冲击
  • 量子计算调试新突破:Bloch向量断言技术详解
  • 3个技巧快速掌握AI翻唱生成:从RVC模型到专业级歌曲转换
  • 95后必备:大模型评测研究员/技术PM高薪岗位,上海/北京等你来!
  • 基于ESP32-C3与LoRa的I²C总线无线桥接器设计与实现
  • Imagine Dragons将亮相阿布扎比大奖赛
  • 从零打造吉他效果器:软硬削波、哇音与晶体管过载电路全解析
  • 在Ubuntu 20.04上编译BetaFlight固件,给AOCODARC-F7MINI飞控刷机(保姆级教程)
  • 现在这情况,我劝大家提前做好准备。。
  • 【DeepSeek协议识别黄金标准】:基于AST+语义指纹的98.7%准确率识别模型首次开源披露
  • 基于GPS授时的精准时钟DIY:从卫星信号到数码管显示
  • 从Excel到3D图:一份内部数据的K-Means聚类与可视化完整实战记录(避坑xlrd与编码)
  • 瑞德克斯平台:从风险提示看平台责任意识
  • 【Spring Boot 认证登录注册模块全解析】:JWT+BCrypt+Redis 企业级实践
  • DELL G3装Ubuntu后WiFi挂了?手把手教你精准查询网卡型号并找对驱动(避坑指南)
  • 告别游戏卡顿!保姆级教程:在Win10上彻底搞定Antimalware Service高占用
  • 趋势科技提醒注意已遭利用的 Apex One 0day 漏洞
  • zotero修改:(1)英文作者三人以上出现“等”
  • 文档格式兼容性挑战与渐进式渲染优化:docxjs库的Web文档渲染架构解析
  • 智能手机多摄像头高光谱成像系统设计与实现
  • 告别外部中断!用EnableInterrupt库轻松搞定Arduino Nano多通道PWM读取(附完整代码)
  • 从频域到时域:聊聊宽带波束形成的两种实现路径与工程选型心得
  • Unity性能适配实战:用SystemInfo判断玩家设备,动态调整画质和特效(附完整代码)
  • Linux下MariaDB 10安装与配置指南
  • 基于OTA芯片的三相正弦波压控振荡器设计与实现