C++设计模式_18_State 状态模式
State和Memento被归为“状态变化”模式。
文章目录
- 1. “状态变化”模式
- 1.1 典型模式
- 2. 动机 (Motivation)
- 3. 代码演示State 状态模式
- 3.1 常规方式
- 3.2 State 状态模式
- 4. 模式定义
- 5. 结构( Structure )
- 6. 要点总结
- 7. 其他参考
1. “状态变化”模式
- 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?同时又维持高层模块的稳定?“状态变化”模式为这一问题提供了一种解决方案。
1.1 典型模式
- State
- Memento
2. 动机 (Motivation)
- 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。
- 如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?
3. 代码演示State 状态模式
3.1 常规方式
假设有一个网络的应用,会根据网路状态做一些调整,比如网络有打开,关闭,连接三种状态
enum NetworkState
{Network_Open,Network_Close,Network_Connect,
};
下面的类为网络的应用,NetworkState state;
是网络的状态,Operation1()在打开状态Network_Open下具有不同的行为,执行完后,最后状态变为close…;Operation2()则是另外的行为,在打开状态Network_Open下具有不同的行为,执行完后,最后状态变为Network_Connect…。
整体代码如下:
class NetworkProcessor{NetworkState state;public:void Operation1(){if (state == Network_Open){//**********state = Network_Close;}else if (state == Network_Close){//..........state = Network_Connect;}else if (state == Network_Connect){//$$$$$$$$$$state = Network_Open;}}public void Operation2(){if (state == Network_Open){//**********state = Network_Connect;}else if (state == Network_Close){//.....state = Network_Open;}else if (state == Network_Connect){//$$$$$$$$$$state = Network_Close;}}public void Operation3(){}
};
动机 (Motivation)中提到的“对象的状态如果改变,其行为也会随之而发生变化”,Operation1()中的if…else已经很清楚的表明了Operation1根据状态不同而行为不同。
上面的写法会有什么问题?看到这段代码,大家可能会有似曾相识的感觉,根据上面代码来看和Strategy模式时碰到的问题是一样的,代码中出现了很多的if…else,而且if…else是有关业务状态。Strategy模式告诉我们对于这种情况我们应该问一个为什么?
以下的枚举类型,以后会不会有其他的类型出现?如果添加了一种新的状态,假设增加了Network_Wait
,之前的代码如何更改?
enum NetworkState
{Network_Open,Network_Close,Network_Connect,Network_Wait,
};
增加了新的状态之后,在不同的operation中需要增加新的if…else。这样的做法显然是违背了开闭原则
。即需求的变更导致需要在代码中不断地去改这些地方。
3.2 State 状态模式
上面的做法肯定是不好的,好的做法是参考Strategy模式的经验,先提抽象基类,把枚举类型转换为一个抽象基类
class NetworkState{public:NetworkState* pNext;virtual void Operation1()=0;virtual void Operation2()=0;virtual void Operation3()=0;virtual ~NetworkState(){}
};
所有跟状态有关的操作Operation1()等全部变为状态对象的行为,并利用多态的虚函数来进行表达,并塞入一个NetworkState* pNext;
状态对象的指针。
创建OpenState
,其中使用了前面所讲的单例模式,在状态中倾向使用单例模式,因为状态的对象没必要有多个。此处Operation1()里面省略部分的内容是与上面常规的方式是有差别的,核心逻辑肯定是类似的,经过Operation1()之后就变为close状态,此时pNext = CloseState::getInstance()
换的是一个对象,而不是一个枚举。总之是将状态相关的操作,全部编码到一个状态对象中。
如果是在open状态,而且执行的是Operation1()的时候会执行怎样的行为,执行完之后下一个状态会切换为哪个状态。
class OpenState :public NetworkState{static NetworkState* m_instance;
public:static NetworkState* getInstance(){if (m_instance == nullptr) {m_instance = new OpenState();}return m_instance;}void Operation1(){//**********pNext = CloseState::getInstance();}void Operation2(){//..........pNext = ConnectState::getInstance();}void Operation3(){//$$$$$$$$$$pNext = OpenState::getInstance();}};
以此类推,得到了close状态等,需要做状态对象。
整个网络应用就改写为状态对象,而不是枚举字段,这种方式与strategy处理的手法是异曲同工。Operation1()
中先做收集参数工作,调用pState->Operation1();
(虚函数的本质是运行时的if…else,在运行时会判断pState的指针如果指向的是open状态,就会调用open状态的operation1;如果指向的是不同状态就会执行对应状态的operation1),执行完之后就让其等于下一个状态 pState = pState->pNext;
,下一个状态是Operation1()内部pNext = ConnectState::getInstance();
更改的状态,我本身不用管。
class NetworkProcessor{NetworkState* pState;public://构造器中初始化pStateNetworkProcessor(NetworkState* pState){this->pState = pState;}void Operation1(){//...pState->Operation1();pState = pState->pNext;//...}void Operation2(){//...pState->Operation2();pState = pState->pNext;//...}void Operation3(){//...pState->Operation3();pState = pState->pNext;//...}};
上面这样做的好处是与Strategy异曲同工。当状态增加的时候,假如增加一个WaitState,仍然是像上面操作,写WaitState里的operation,而NetworkProcessor里是不用改变的,NetworkProcessor不关心其是什么状态,只关心状态上的行为是对的,状态行为里去自己去管理状态转换关系,是一种扩展的方法。
下为整体代码:
class NetworkState{public:NetworkState* pNext;virtual void Operation1()=0;virtual void Operation2()=0;virtual void Operation3()=0;virtual ~NetworkState(){}
};class OpenState :public NetworkState{static NetworkState* m_instance;
public:static NetworkState* getInstance(){if (m_instance == nullptr) {m_instance = new OpenState();}return m_instance;}void Operation1(){//**********pNext = CloseState::getInstance();}void Operation2(){//..........pNext = ConnectState::getInstance();}void Operation3(){//$$$$$$$$$$pNext = OpenState::getInstance();}};class CloseState:public NetworkState{ }
//...//扩展
class WaitState:public NetworkState{ }class NetworkProcessor{NetworkState* pState;public:NetworkProcessor(NetworkState* pState){this->pState = pState;}void Operation1(){//...pState->Operation1();pState = pState->pNext;//...}void Operation2(){//...pState->Operation2();pState = pState->pNext;//...}void Operation3(){//...pState->Operation3();pState = pState->pNext;//...}};
4. 模式定义
允许一个对象在其内部状态改变时改变它的行为。从而使对象看起来似乎修改了其行为。
----《设计模式》GoF
5. 结构( Structure )
上图是《设计模式》GoF中定义的State 状态模式的设计结构。结合上面的代码看图中最重要的是看其中稳定和变化部分,也就是下图中红框和蓝框框选的部分。
State 状态模式和Strategy非常像,State中放的是一个行为,一般是多个行为,一个行为时和Strategy没什么两样的。
6. 要点总结
-
State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦。
-
为不同的状态引入不同的对象使得状态转换变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换是原子性的–即要么彻底转换过莱,要么不转换
openstate值关心三个操作之后的下一个状态是什么,不需要太多的想更多耦合的情况。相比最初代码实现解耦。
- 如果state对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销
State 状态模式讲下来和Strategy非常像,当最后往回看的时候,你会发现很多模式越来越像,相同点越来越多,学习模式最后会发现,这些模式之间差别会很小,会忘掉具体的模式名称。不一定非要纠结模式的名称,只是松耦合设计原则的演化。你可以将State 状态模式和Strategy看做同一个模式,出现的问题是If…else,枚举,怎么转,利用多态方式实现运行时的改变。掌握了这些,具体是什么模式就没那么重要了。
7. 其他参考
C++设计模式——状态模式
相关文章:

C++设计模式_18_State 状态模式
State和Memento被归为“状态变化”模式。 文章目录 1. “状态变化”模式1.1 典型模式 2. 动机 (Motivation)3. 代码演示State 状态模式3.1 常规方式3.2 State 状态模式 4. 模式定义5. 结构( Structure )6. 要点总结7. 其他参考 1. “状态变化”模式 在组件构建过程中…...

详解final, abstract, interface关键字
一.final关键字 1.final关键字介绍 ——final关键字可以去修饰类、方法、属性和局部变量 2.final关键字的作用 1)final修饰类,这个类不能被其他类继承 2)final修饰方法,方法不能被重写 3)final修饰属性,属…...
统计特殊四元组
题记: 给你一个 下标从 0 开始 的整数数组 nums ,返回满足下述条件的 不同 四元组 (a, b, c, d) 的 数目 : nums[a] nums[b] nums[c] nums[d] ,且a < b < c < d 示例 1: 输入: nums [1,2,3…...

腾讯云轻量应用服务器“镜像”怎么选择合适?
腾讯云轻量应用服务器镜像怎么选择?如果是用来搭建网站可以选择宝塔Linux面板腾讯云专享版,镜像系统根据实际使用来选择,腾讯云百科txybk.com来详细说下腾讯云轻量应用服务器镜像的选择方法: 腾讯云轻量应用服务器镜像选择 轻量…...
Ruby模块和程序组织
和类一样,模块是一组方法和常量的集合。 和类不同,模块没有实例,取而代之的是可以将特殊模块的功能添加到一个类或者指定对象之中。 Class类是Module类的一个子类,因此每一个类对象也是一个模块对象 一、模块创建和基础应用 编写…...
14、SpringCloud -- WebSocket 实时通知用户
目录 实时通知用户需求:代码:前端:后端:WebSocket创建 websocket-server 服务添加依赖:配置 yml 和 启动类:前端:后端代码:注意:测试:总结:实时通知用户 需求: 用户订单秒杀成功之后,对用户进行秒杀成功通知。 弹出个提示框来提示。 代码: 前端:...

智能井盖传感器推荐,万宾科技助力城市信息化建设
随着科技产品更新换代进程加快,人工智能在人们日常生活之中逐渐普及开来,深入人们生活的方方面面,影响城市基础设施建设工程。例如在大街小巷之中的井盖作为城市基础建设的一个重要部分,一旦出现松动倾斜或凸起等异常问题…...

3D模型格式转换工具HOOPS Exchange对工业级3D产品HOOPS的支持与应用
一、概述 HOOPS Exchange是一套高性能模型转换软件库,可以给软件提供强大的模型的导入和导出功能,我们可以将其单独作为转换工具使用,也可以将其集成到自己的软件中。 同样,HOOPS 的其它产品,也离不开HOOPS Exchange…...

table 表体滚动, 表头、表尾固定
在开发报表中,如果报表数据行过多页面无法全部显示,或者内容溢出div,需要把表头和表尾固定表体滚动这样就可以在页面上全部显示,并且不会溢出div 效果:最终实现效果 代码:<!DOCTYPE html> <html&g…...

第57篇-某钩招聘网站加密参数分析【2023-10-31】
声明:该专栏涉及的所有案例均为学习使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!如有侵权,请私信联系本人删帖! 文章目录 一、前言二、网站分析1.X-S-HEADER参数2.请求参数data3.响应机密值data一、前言 网址: aHR0cHM6Ly93d3cubGFnb3UuY29t…...

C语言数据结构之数据结构入门
目录 数据结构介绍 数据结构发展史 何为算法 数据结构基础 基本概念和术语 四大逻辑结构(Logic Structure) 数据类型 理解复杂度概念 时间空间复杂度定义 度量时间复杂度的方法 程序运行时的内存与地址 编程预备 数据结构介绍 数据结构发展…...

如何知道服务器的某个端口是否打开
1、telnet 命令:telnet ip port,port即端口,我们一般最常见的命令就是telnet,但是telnet使用的是tcp协议,换句话说telnet只能检测tcp的这个端口打开了没 若是端口打开,会出现下列信息 失败的是这个 如…...

【ICCV‘23】One-shot Implicit Animatable Avatars with Model-based Priors
文章目录 前置知识 前置知识 1)SMPL模型 \quad SMPL这类方法只建模穿很少衣服的人体(裸体模型),它只能刻画裸体角色的动画,并不能刻画穿衣服的人体的动画 2)data-efficient \quad 这个词推荐用ÿ…...

关于息肉检测和识别项目的总结
前言 整体的思路:首先息肉数据集分为三类: 1.正常细胞 2. 增生性息肉 3. 肿瘤要想完成这个任务,首先重中之重是分割任务,分割结果的好坏, 当分割结果达到一定的准确度后,开始对分割后的结果进行下游分类…...

Jetson Xavier NX FFmpeg支持硬件编解码
最近在用Jetson Xavier NX板子做视频处理,但是CPU进行视频编解码,效率比较地下。 于是便考虑用硬解码来对视频进行处理。 通过jtop查看,发现板子是支持 NVENC硬件编解码的。 1、下载源码 因为需要对ffmpeg进行打补丁修改,因此需要编译两份源码 1.1、编译jetson-ffmpeg …...

518抽奖软件,为什么说比别的抽奖软件更美观精美?
518抽奖软件简介 518抽奖软件,518我要发,超好用的年会抽奖软件,简约设计风格。 包含文字号码抽奖、照片抽奖两种模式,支持姓名抽奖、号码抽奖、数字抽奖、照片抽奖。(www.518cj.net) 精致美观功能 字体平滑无锯齿图片放大后清晰…...
React的组件学习
React的组件学习 参考资料:https://zh-hans.react.dev/learn/your-first-component 一、定义组件 export default function Profile() {return (<imgsrc"https://i.imgur.com/MK3eW3Am.jpg"alt"Katherine Johnson"/>) }以下是构建组件…...

uni-app配置微信开发者工具
一、配置微信开发者工具路径 工具->设置->运行配置->小程序运行配置->微信开发者工具路径 二、微信开发者工具开启服务端口...

肺癌不再是老年病:33岁作家的离世引发关注,有这些情况的请注意
近期,90后网络小说家七月新番和26岁男艺人蒋某某因肺癌去世,引发关注。他们都没有吸烟习惯,那么他们为什么会得肺癌呢?浙大二院呼吸内科副主任医师兰芬说,现在年轻人熬夜、加班导致身体过劳,在劳累情况下身…...

【兔子王赠书第4期】用ChatGPT轻松玩转机器学习与深度学习
文章目录 前言机器学习深度学习ChatGPT推荐图书粉丝福利尾声 前言 兔子王免费赠书第4期来啦,突破传统学习束缚,借助ChatGPT的神奇力量,解锁AI无限可能! 机器学习 机器学习是人工智能领域的一个重要分支,它的目的是让…...

利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
ffmpeg(四):滤镜命令
FFmpeg 的滤镜命令是用于音视频处理中的强大工具,可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下: ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜: ffmpeg…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...