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

【设计模式】状态模式

文章目录

  • 前言
  • 状态模式
  • 1、状态模式介绍
    • 1.1 存在问题
    • 1.2 解决问题
    • 1.3 状态模式结构图
  • 2、具体案例说明状态模式
    • 2.1 不使用状态模式
    • 2.2 使用状态模式
  • 3、状态模式总结

前言

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然,如果这个状态判断很简单,那就没必要用状态模式了。

状态模式

1、状态模式介绍

状态模式(State)是一种行为型设计模式,当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。(可以说当到饭点了,你就会主动去找饭吃)

1.1 存在问题

通常情况下,一个对象的行为会随着其内部状态的改变而发生变化,这些行为通常被包含在相应的if...else 语句中,导致代码难以维护和扩展。

1.2 解决问题

适用状态模式判断状态是否改变。

状态模式的思想是将每种可能的状态都封装成一个类,因此可以在不改变原有代码的前提下动态地改变对象的行为。状态模式提供了一种简单的实现方式,即通过定义一个state接口,再定义具体的state子类,Context类中持有一个state的引用,在不同状态下,分别委托给不同的state处理相应的请求。这种实现方式将原来的大类拆分成多个小类,使得系统更加灵活、易于扩展,符合开闭原则。

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然,如果这个状态判断很简单,那就没必要用状态模式了。

1.3 状态模式结构图

在这里插入图片描述

  1. State类,抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为。
/*** 抽象状态类*/
public abstract class State {public abstract void  handle(Context context);
}
  1. Context类,维护一个ConcreteState子类的实例,这个实例定义当前的状态。
public class Context {private State state;/*** 初始化当前状态* @param state*/public Context(State state) {this.state = state;}public State getState() {return state;}public void setState(State state) {this.state = state;System.out.println("当前状态:"+this.state.getClass().getName());}/*** 对请求做处理,并设置下一个状态*/public void request(){this.state.handle(this);}
}
  1. ConcreteState类,具体状态,每一个子类实现一个与Context的一个状态相关的行为。
/*** 具体状态类A*/public class ConcreteStateA extends State{/*** 设置ConcreteStateA的下一个状态是ConcreteStateB* @param context*/@Overridepublic void handle(Context context) {context.setState(new ConcreteStateB());}
}
/*** 具体状态类B*/
public class ConcreteStateB extends State {/*** 设置ConcreteStateB的下一个状态是ConcreteStateA* @param context*/@Overridepublic void handle(Context context) {context.setState(new ConcreteStateA());}
}
  1. 客户端发起请求调用
public class ClientTest {public static void main(String[] args) {// 初始状态为ConcreteStateAContext context = new Context(new ConcreteStateA());// 不断请求,不断改变请求状态context.request();context.request();context.request();}
}

输出结果:

在这里插入图片描述

2、具体案例说明状态模式

案例:不同的工作时间做不同的事情

2.1 不使用状态模式

/*** 工作类*/public class Work {// 时间private int hour;// 是否完成工作任务private boolean workFinished = false;public int getHour() {return hour;}public void setHour(int hour) {this.hour = hour;}public boolean getWorkFinished() {return workFinished;}public void setWorkFinished(boolean workFinished) {this.workFinished = workFinished;}/*** 工作时间段*/public void workTime() {if (hour < 12) {System.out.println("当前时间" + hour + "点,上午工作,精神百倍。");} else if (hour < 13) {System.out.println("当前时间" + hour + "点,饿了,午饭,犯困,午休。");} else if (hour < 17) {System.out.println("当前时间" + hour + "点,下午状态还可以,继续敲代码。");} else {if (workFinished) {System.out.println("当前时间" + hour + "点,下班回家了!!,愉快结束一天。");} else {if (hour < 21) {System.out.println("当前时间" + hour + "点,又开始加班,疲累之极,**加班。");} else {System.out.println("当前时间" + hour + "点,睡觉时间到了,躺床就睡着。");}}}}
}

客户端

public class WorkClient {public static void main(String[] args) {Work work = new Work();// 早上work.setHour(9);work.workTime();work.setHour(11);work.workTime();// 中午work.setHour(12);work.workTime();//下午work.setHour(13);work.workTime();work.setHour(14);work.workTime();work.setHour(17);//工作未完成work.setWorkFinished(false);// 加班work.workTime();work.setHour(19);work.workTime();work.setHour(22);work.workTime();}
}

结果显示:
在这里插入图片描述

有没有发现什么问题?workTime()这个方法已经违背了开闭原则了,每次都要修改这个方法才得以扩展新的功能

2.2 使用状态模式

具体的结构类图
在这里插入图片描述

State类:

/*** 状态类*/
public abstract class State {public abstract void workTime(Work work);
}

Work工作类:

/*** 工作类*/
public class Work {/*** 时间点*/private int hour;/*** 是否完成工作任务-是否到达下班条件*/private boolean workFinished = false;/*** 当前状态-设置下一个状态*/private State currentState;/*** 初始化状态-上午*/public Work() {currentState = new ForenoonState();}/*** 工作时间段-显示当前状态,并切换到下一个状态*/public void workTime() {this.currentState.workTime(this);}public State getCurrentState() {return currentState;}public void setCurrentState(State currentState) {this.currentState = currentState;}public int getHour() {return hour;}public void setHour(int hour) {this.hour = hour;}public boolean getWorkFinished() {return workFinished;}public void setWorkFinished(boolean workFinished) {this.workFinished = workFinished;}
}

早上具体状态:

/*** 早上具体状态*/
public class ForenoonState extends State {@Overridepublic void workTime(Work work) {if (work.getHour() < 12) {System.out.println("当前时间" + work.getHour() + "点,上午工作,精神百倍。");} else {// 超过12点就转入中午状态work.setCurrentState(new NoonState());work.workTime();}}
}

中午具体状态:

/*** 中午状态*/
public class NoonState extends State {@Overridepublic void workTime(Work work) {if (work.getHour() < 13) {System.out.println("当前时间" + work.getHour() + "点,饿了,午饭,犯困,午休。");} else {// 超过13点就转入下午工作状态work.setCurrentState(new AfternoonState());work.workTime();}}
}

下午具体状态:

/*** 下午具体状态*/
public class AfternoonState extends State {@Overridepublic void workTime(Work work) {if (work.getHour() < 17) {System.out.println("当前时间" + work.getHour() + "点,下午状态还可以,继续敲代码。");} else {// 超时17点就进去傍晚工作时间点work.setCurrentState(new EveingState());work.workTime();}}
}

任务完成,按时下班状态类

/*** 按时下班*/
public class RestState extends State {@Overridepublic void workTime(Work work) {System.out.println("当前时间:" + work.getHour() + "点,下班回家咯!!");}
}

工作任务未完成,加班“累”:

/*** 具体加班类*/
public class EveingState extends State {@Overridepublic void workTime(Work work) {if (work.getWorkFinished()) {// 工作完成,下班work.setCurrentState(new RestState());work.workTime();} else {// 工作没有完成则继续加班if (work.getHour() < 21) {System.out.println("当前时间" + work.getHour() + "点,又开始加班,疲累之极,**加班。");} else {// 到点睡觉work.setCurrentState(new SleepingState());work.workTime();}}}
}

睡觉类:

/*** 睡觉状态*/
public class SleepingState extends State {@Overridepublic void workTime(Work work) {System.out.println("当前时间" + work.getHour() + "点,睡觉时间到了,躺床就睡着。");}
}

客户端同上
最终的结果也同上

虽然结果相同,但是我们的程序变得更加灵活,比如公司要求在20点之前必须离开公司, 此时我们就要新增一个“强制下班类”,并改动一下
“晚间工作状态”类的判断就可以 了。而这是不影响其他状态的代码的。

实现加班类修改如下:

/*** 具体加班类*/
public class EveingState extends State {@Overridepublic void workTime(Work work) {if (work.getWorkFinished()) {// 工作完成,下班work.setCurrentState(new RestState());work.workTime();} else {// 工作没有完成则继续加班work.setCurrentState(new ForcedLiveWork());work.workTime();}}
}

强制下班类:

/*** 强制下班*/
public class ForcedLiveWork extends State {@Overridepublic void workTime(Work work) {if (work.getHour() < 20) {System.out.println("当前时间" + work.getHour() + "点,公司规定,此刻必须要离开公司了。");} else {// 到点睡觉work.setCurrentState(new SleepingState());work.workTime();}}
}

实现起来并不困难,只需增加一个类,再对去修改判断条件,这样就不会影响到其他状态的代码

3、状态模式总结

状态模式的优点包括:

  1. 将状态转换和行为隔离开来,使得状态变化时只需要改变状态类对象即可,无需修改Context类,从而保证了系统的灵活性、可扩展性和可维护性。
  2. 在状态模式中,每个状态都被封装在一个类中,增加新的状态类很方便,符合开闭原则。
  3. 通过引入抽象状态类和抽象环境类,可以很好地解决代码的耦合问题。状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖
  4. 状态模式使得状态转换显式化了,独立于具体的状态类之外,更符合面向对象的设计思想。
  5. 对于状态机的实现,状态模式提供了一种设计思路和方法。
  6. 将特定的状态相关的行为都放入一个对象中,由于所有与 状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可 以很容易地增加新的状态和转换。(消除大量的条件判断语句)

状态模式的缺点包括:

  1. 由于引入了多个子类,因此在一定程度上增加了系统的复杂度,使得系统抽象层次增加,设计难度加大。
  2. 如果状态改变很频繁,则会导致系统中类的数量增加,从而增加系统的维护难度。
  3. 如果状态比较多,且状态之间的转换比较复杂,容易造成代码的混乱和不易维护。

状态模式适用场景:

  1. 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态 改变它的行为时,可以考虑使用状态模式。
  2. 当对象的行为随着对象内部状态的变化而发生变化时,可以考虑使用状态模式。例如,电视机有开机、关机、切换频道等状态,根据不同的状态做出相应的响应,使用状态模式可以方便地实现。
  3. 当系统中存在多种状态且状态之间存在转换关系时,可以考虑使用状态模式。例如,一个产品订单在待支付、已支付、已取消等多个状态之间转换,可以使用状态模式来实现。
  4. 当需要对状态进行动态修改时,可以考虑使用状态模式。例如,在游戏中,玩家角色根据当前的状态有不同的技能和装备,而这些状态是可以通过游戏中获得的物品进行修改的,这时候就可以使用状态模式。
  5. 当状态转换规则比较复杂或需要进行扩展时,可以考虑使用状态模式。状态模式将状态转换规则封装在具体状态类中,可以方便地对状态转换规则进行修改和扩展。
  6. 当希望避免使用大量的if else语句,提高代码可读性和可维护性时,可以考虑使用状态模式。状态模式使得代码易于扩展和修改,并且降低了代码的耦合度,更符合面向对象的设计原则。

相关文章:

【设计模式】状态模式

文章目录 前言状态模式1、状态模式介绍1.1 存在问题1.2 解决问题1.3 状态模式结构图 2、具体案例说明状态模式2.1 不使用状态模式2.2 使用状态模式 3、状态模式总结 前言 状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示…...

内核驱动支持浮点数运算

最近在调 iio 下的 ICM42686 驱动&#xff0c;因项目求需要在驱动对加速度和陀螺raw数据进行换算&#xff0c;避免不了浮点运算。内核编译时出现了报错&#xff0c;提示如下&#xff1a; drivers/iio/imu/tdk_icm42686/icm42686.o: In function gyro_data2float: /home/share/…...

Flink学习(一)

分布式计算框架 Java可以使用分布式计算来处理大规模的数据和计算任务,提高计算效率和性能。以下是一些Java分布式计算的例子: Apache Hadoop:Hadoop是一个开源的分布式计算框架,可以处理大规模数据集的分布式存储和处理。它使用Java编写,可以在分布式环境中运行MapReduc…...

linux 常用命令awk

AWK 是一种处理文本文件的语言&#xff0c;是一个强大的文本分析工具。之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&#xff0c;Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。 AWK用法 awk 用法&#xff1a;awk pattern {action} files 1.RS, ORS, F…...

MySQL学习---15、流程控制、游标

1、流程控制 解决复杂问题不可能是通过一个SQL语句完成&#xff0c;我们需要执行多个SQL操作。流程控制语句的作用就是控制存储过程中SQL语句的执行顺序&#xff0c;是我们完成复杂操作必不可少的一部分。只要是执行的程序&#xff0c;流程就分为三大类&#xff1a; 1、顺序结…...

信息调查的观念

每次做一件事前都要把这件事调查清楚&#xff0c;比如考一门科目我们要把和这门科目有关的资源都收集起来&#xff0c;然后把再从中筛选出有用的信息&#xff0c;如数值计算方法我们在考试前就可以把b站有关的学习资源网课或者前人总结的考试经验做个收集总结&#xff0c;做出对…...

leetcode 337. 打家劫舍 III

题目链接&#xff1a;leetcode 337 1.题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口&#xff0c;我们称之为 root 。 除了 root 之外&#xff0c;每栋房子有且只有一个“父“房子与之相连。一番侦察之后&#xff0c;聪明的小偷意识到“这个地方的所有房屋的…...

基于Docker的深度学习环境NVIDIA和CUDA部署以及WSL和linux镜像问题

基于Docker的深度学习环境部署 1. 什么是Docker&#xff1f;2. 深度学习环境的基本要求3. Docker的基本操作3.1 在Windows上安装Docker3.2 在Ubuntu上安装Docker3.3 拉取一个pytorch的镜像3.4 部署自己的项目3.5 导出配置好项目的新镜像 4. 分享新镜像4.1 将镜像导出为tar分享给…...

c#中slice,substr,substring区别

1. 都使用一个参数&#xff1a; //栗子数据 var arr [1,2,3,4,5,6,7], str "helloworld!"; //防止空格干扰&#xff0c;不用带空格的&#xff0c;注意这里有个&#xff01;号也算一位 console.log(str.slice(1)); //elloworld! console.log(str.substring(1)); //…...

java语言里redis在项目中使用场景,每个场景的样例代码

Redis是一款高性能的NoSQL数据库&#xff0c;常被用于缓存、消息队列、计数器、分布式锁等场景。以下是50个Redis在项目中使用的场景以及对应的样例代码和详细说明&#xff1a; ##1、缓存&#xff1a;将查询结果缓存在Redis中&#xff0c;下次查询时直接从缓存中获取&#xff…...

Mongo集合操作

2、创建切换数据库 2.1 默认数据库 mongo数据库和其他类型的数据库一样&#xff0c;可以创建数据库&#xff0c;且可以创建多个数据库。 mongo数据库默认会有四个数据库&#xff0c;分别是 admin&#xff1a;主要存储MongoDB的用户、角色等信息 config&#xff1a;主要存储…...

ConvTranspose2d 的简单例子理解

文章目录 参考基础概念output_padding 简单例子&#xff1a; stride2step1step2step3 参考 逆卷积的详细解释ConvTranspose2d&#xff08;fractionally-strided convolutions)nn.ConvTranspose2d的参数output_padding的作用torch.nn.ConvTranspose2d Explained 基础概念 逆卷…...

酒精和肠内外健康:有帮助还是有害?

谷禾健康 酒精与健康 饮酒作为一种特殊的文化形式&#xff0c;在我们国家有其独特的地位&#xff0c;在几千年的发展中&#xff0c;酒几乎渗透到日常生活、社会经济、文化活动之中。 据2018年发表的《中国饮酒人群适量饮酒状况》白皮书数据显示&#xff0c;中国饮酒人群高达6亿…...

SylixOS Shell下操作环境变量方法

系统启动后会在内核中生成一份默认的环境变量&#xff0c;环境变量名和默认值由源程序决定。系统启动后如果文件系统中存在有效的/etc/profile文件&#xff0c;则还会自动读取文件中的内容&#xff0c;并导入到Shell环境中&#xff0c;覆盖对应变量或增加新的变量。程序运行时&…...

【dfs解决分组问题-两道例题——供佬学会!】(A元素是放在已经存在的组别中,还是再创建一个更好?--小孩子才做选择,dfs直接两种情况都试试)

问题关键就是&#xff1a; 一个点&#xff0c;可能 新开一个组 比 放到已经存在的组 更划算 因为后面的数据&#xff0c;我们遍历之前的点时&#xff0c;并不知道 所以我们应该针对每个点&#xff0c;都应该做出一个选择就是 新开一个元组或者放到之前的元组中&#xff0c;都尝…...

使用Hexo在Github上搭建个人博客

使用Hexo在Github上搭建个人博客 1. 安装Node和git2. 安装Hexo3. Git与Github的准备工作4. 将Hexo部署到Github5. 开始写作 1. 安装Node和git 在Mac上安装Node.js可以使用Homebrew&#xff0c;使用以下命令安装&#xff1a; brew install node使用以下命令安装Git&#xff1a; …...

【面试题】面试官:说说你对 CSS 盒模型的理解

前言 CSS 盒模型是 CSS 基础的重点难点&#xff0c;因此常被面试官们拿来考察候选人对前端基础的掌握程度&#xff0c;这篇文章将对 CSS 盒模型知识点进行全面的梳理。 我们先看个例子&#xff1a;下面的 div 元素的总宽度是多少呢&#xff1f; js <!DOCTYPE html> &…...

【ROS2】学习笔记

1. 基础概念 1.1 执行单元 1.1.1 executable——执行程序 executable表示针对某个目标的程序执行流程&#xff0c;一个executable可以启动多个node&#xff1b; 1.1.2 node——“进程” node其实就是进程的意思&#xff1b; ROS2允许同时启动两个相同的node&#xff0c;&a…...

Springboot +Flowable,流程表单应用之外置表单(JSON形式)(二)

一.简介 整体上来说&#xff0c;我们可以将Flowable 的表单分为三种不同的类型&#xff1a; 动态表单 这种表单定义方式我们可以配置表单中每一个字段的可读性、可写性、是否必填等信息&#xff0c;不过不能定义完整的表单页面。外置表单 外置表单我们只需要定义一下表单的 k…...

JavaScript如何使用if语句

JavaScript的if语句可以让我们根据某些条件来执行不同的代码块。使用if语句的基本思路是将要执行的代码放在括号内&#xff0c;并使用if关键字进行匹配。下面是一些例子&#xff1a; 简单的if语句&#xff1a; let age 18; if (age > 18) { console.log("You are…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式&#xff0c;以r为参数&#xff1a; p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]&#xff1b; 此多项式的根为&#xff1a; 尽管看起来这个多项式是特殊的&#xff0c;其实一般的三次多项式都是可以通过线性变换化为这个形式…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

动态规划-1035.不相交的线-力扣(LeetCode)

一、题目解析 光看题目要求和例图&#xff0c;感觉这题好麻烦&#xff0c;直线不能相交啊&#xff0c;每个数字只属于一条连线啊等等&#xff0c;但我们结合题目所给的信息和例图的内容&#xff0c;这不就是最长公共子序列吗&#xff1f;&#xff0c;我们把最长公共子序列连线起…...

【工具教程】多个条形码识别用条码内容对图片重命名,批量PDF条形码识别后用条码内容批量改名,使用教程及注意事项

一、条形码识别改名使用教程 打开软件并选择处理模式&#xff1a;打开软件后&#xff0c;根据要处理的文件类型&#xff0c;选择 “图片识别模式” 或 “PDF 识别模式”。如果是处理包含条形码的 PDF 文件&#xff0c;就选择 “PDF 识别模式”&#xff1b;若是处理图片文件&…...