Java 设计模式——状态模式

目录

  • 1.概述
  • 2.结构
  • 3.案例实现
    • 3.1.抽象状态类
    • 3.2.具体状态类
    • 3.3.上下文类
    • 3.4.测试
  • 4.优缺点
  • 5.使用场景

1.概述

【例】通过按钮来控制一个电梯的状态,电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。其类图如下:
在这里插入图片描述
具体实现代码如下:

ILift.java

public interface ILift {//定义四个电梯状态的常量int OPENING_STATE = 1;int CLOSING_STATE = 2;int RUNNING_STATE = 3;int STOPPING_STATE = 4;//设置电梯状态的功能void setState(int state);//电梯操作功能void open();void close();void run();void stop();
}

Lift.java

//电梯类(ILift 接口的实现类)
public class Lift implements ILift {//声明一个记录当前电梯的状态private int state;public void setState(int state) {this.state = state;}public void open() {switch (state) { //当前电梯状态case OPENING_STATE ://什么事都不做break;case CLOSING_STATE :System.out.println("电梯打开了...");//设置当前电梯状态为开启状态setState(OPENING_STATE);break;case STOPPING_STATE :System.out.println("电梯打开了...");//设置当前电梯状态为开启状态setState(OPENING_STATE);break;case RUNNING_STATE ://什么事都不做break;}}public void close() {switch (this.state) {case OPENING_STATE:System.out.println("电梯关门了...");	//只有开门状态可以关闭电梯门,可以对应电梯状态表来看this.setState(CLOSING_STATE);		//关门之后电梯就是关闭状态了break;case CLOSING_STATE://do nothing 	//已经是关门状态,不能关门break;case RUNNING_STATE://do nothing 	//运行时电梯门是关着的,不能关门break;case STOPPING_STATE://do nothing 	//停止时电梯也是关着的,不能关门break;}}public void run() {switch (this.state) {case OPENING_STATE:	//电梯不能开着门就走//do nothingbreak;case CLOSING_STATE:	//门关了,可以运行了System.out.println("电梯开始运行了。。。");this.setState(RUNNING_STATE);//现在是运行状态break;case RUNNING_STATE://do nothing 已经是运行状态了break;case STOPPING_STATE:System.out.println("电梯开始运行了。。。");this.setState(RUNNING_STATE);break;}}public void stop() {switch (this.state) {case OPENING_STATE: 	//开门的电梯已经是是停止的了(正常情况下)//do nothingbreak;case CLOSING_STATE:		//关门时才可以停止System.out.println("电梯停止了。。。");this.setState(STOPPING_STATE);break;case RUNNING_STATE:		//运行时当然可以停止了System.out.println("电梯停止了。。。");this.setState(STOPPING_STATE);break;case STOPPING_STATE://do nothingbreak;}}
}

Client.java

public class Client {public static void main(String[] args) {//创建电梯对象Lift lift = new Lift();//设置当前电梯的状态lift.setState(ILift.OPENING_STATE);//打开lift.open();lift.close();lift.run();lift.stop();}
}

问题分析:

  • 使用了大量的 switch…case 这样的判断(换成 if…else 也是一样),使程序的可读性变差。
  • 扩展性很差,如果新加了断电的状态,则需要修改上面的判断逻辑。

(2)状态模式 (State Pattern) 是一种行为型设计模式,它允许在一个对象内部状态改变时改变它的行为。状态模式将状态封装成独立的类,并将动作委托给代表当前状态的对象,以此实现状态转换时的行为变化。使用状态模式,我们可以将状态转换规则封装在不同状态类中,让上下文对象从复杂的状态转换逻辑中解耦出来,同时利用多态特性让状态转换具有扩展性和灵活性。

2.结构

状态模式包含以下主要角色:

  • 上下文 (Context) 角色:是一个包含状态的对象,它在运行时根据不同的状态改变其行为。上下文对象持有一个对当前状态的引用,并将具体的操作委托给当前状态对象处理。
  • 抽象状态 (Abstract State) 角色:定义了一个公共的接口,用于封装不同状态的行为。可以是一个抽象类或接口,并声明了在不同状态下可能发生的方法。
  • 具体状态 (Concrete State) 角色:实现了抽象状态接口,并定义了在对应状态下具体的行为。每个具体状态类都封装了一种特定状态的行为。

3.案例实现

对上述电梯的案例使用状态模式进行改进。类图如下:
在这里插入图片描述
具体实现代码如下:

3.1.抽象状态类

LiftState.java()

//抽象状态类
public abstract class LiftState {//声明环境角色类变量protected Context context;public void setContext(Context context) {this.context = context;}//电梯开启操作public abstract void open();//电梯关闭操作public abstract void close();//电梯运行操作public abstract void run();//电梯停止操作public abstract void stop();
}

3.2.具体状态类

OpeningState.java

//电梯开启状态类
public class OpeningState extends LiftState {//当前状态要执行的方法public void open() {System.out.println("电梯开启。。。");}public void close() {//修改状态super.context.setLiftState(Context.CLOSING_STATE);//调用当前状态中的context中的close方法super.context.close();}public void run() {//什么都不做}public void stop() {//什么都不做}
}

ClosingState.java

//电梯关闭状态类
public class ClosingState extends LiftState {@Override//电梯门关闭,这是关闭状态要实现的动作public void close() {System.out.println("电梯门关闭...");}//电梯门关了再打开,逗你玩呢,那这个允许呀@Overridepublic void open() {super.context.setLiftState(Context.OPENING_STATE);super.context.open();}//电梯门关了就跑,这是再正常不过了@Overridepublic void run() {super.context.setLiftState(Context.RUNNING_STATE);super.context.run();}//电梯门关着,我就不按楼层@Overridepublic void stop() {super.context.setLiftState(Context.STOPPING_STATE);super.context.stop();}
}

RunningState.java

//电梯运行状态类
public class RunningState extends LiftState {//运行的时候开电梯门?你疯了!电梯不会给你开的@Overridepublic void open() {//do nothing}//电梯门关闭?这是肯定了@Overridepublic void close() {//虽然可以关门,但这个动作不归我执行//do nothing}//这是在运行状态下要实现的方法@Overridepublic void run() {System.out.println("电梯正在运行...");}//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了@Overridepublic void stop() {super.context.setLiftState(Context.STOPPING_STATE);super.context.stop();}
}

StoppingState.java

//电梯停止状态类
public class StoppingState extends LiftState {//停止状态,开门,那是要的!@Overridepublic void open() {//状态修改super.context.setLiftState(Context.OPENING_STATE);//动作委托为CloseState来执行,也就是委托给了 ClosingState 子类执行这个动作super.context.getLiftState().open();}@Overridepublic void close() {//虽然可以关门,但这个动作不归我执行//状态修改super.context.setLiftState(Context.CLOSING_STATE);//动作委托为 CloseState 来执行,也就是委托给了 ClosingState 子类执行这个动作super.context.getLiftState().close();}//停止状态再跑起来,正常的很@Overridepublic void run() {//状态修改super.context.setLiftState(Context.RUNNING_STATE);//动作委托为 CloseState 来执行,也就是委托给了 ClosingState 子类执行这个动作super.context.getLiftState().run();}//停止状态是怎么发生的呢?当然是停止方法执行了@Overridepublic void stop() {System.out.println("电梯停止了...");}
}

3.3.上下文类

Context.java

//环境角色类
public class Context {//定义对应状态对象的常量public final static OpeningState OPENING_STATE = new OpeningState();public final static ClosingState CLOSING_STATE = new ClosingState();public final static RunningState RUNNING_STATE = new RunningState();public final static StoppingState STOPPING_STATE = new StoppingState();//定义一个当前电梯状态变量private LiftState liftState;public LiftState getLiftState() {return liftState;}//设置当前状态对象public void setLiftState(LiftState liftState) {this.liftState = liftState;//设置当前状态对象中的 Context 对象this.liftState.setContext(this);}public void open() {this.liftState.open();}public void close() {this.liftState.close();}public void run() {this.liftState.run();}public void stop() {this.liftState.stop();}
}

3.4.测试

Client.java

public class Client {public static void main(String[] args) {//创建环境角色对象Context context = new Context();//设置当前电梯装填context.setLiftState(new ClosingState());context.open();context.run();context.close();context.stop();}
}

4.优缺点

  • 优点:
    • 将状态封装成独立的类,使得状态转换的逻辑更加清晰,避免了大量的条件判断语句。
    • 将状态和状态切换的行为分离,使得状态转换的代码可扩展和可维护。增加新的状态类或修改现有的状态类时,对上下文对象和其他状态类都没有影响。
    • 增强了代码的可读性和可维护性。每个状态都有自己的类,使得代码具备了良好的结构化,并且易于理解和修改。
    • 通过引入多态性,可以方便地增加新的状态类,而不需要修改现有的代码。
  • 缺点:
    • 如果状态类很多,可能会导致类的数量庞大,增加了代码的复杂性。
    • 状态类的创建和销毁需要维护和管理,可能会增加系统的开销。
    • 当状态转换的逻辑较为简单时,使用状态模式可能会显得过于繁琐。

5.使用场景

(1)状态模式适用于以下情况:

  • 当一个对象的行为取决于其内部状态,并且在不同状态下需要有不同的行为时,可以使用状态模式。例如,订单状态的改变会导致不同的操作和行为,可以使用状态模式来管理订单的状态转换。
  • 当有大量的条件语句用于根据不同状态执行不同操作时,可以使用状态模式来简化代码并提高可维护性。
  • 当一个对象的行为需要根据多个条件进行判断,并且这些条件随着时间的推移可能会发生变化时,可以使用状态模式。通过将每个条件封装为一个状态类,可以方便地添加、修改或删除状态,而不会对其他部分产生影响。
  • 当需要在运行时动态改变对象的行为时,可以使用状态模式。通过改变对象的状态,可以改变其行为,而不需要修改对象的代码。

(2)总之,状态模式适用于需要根据对象的内部状态来改变其行为,并且希望将状态转换逻辑封装在独立的状态类中的情况。它提供了一种清晰、灵活和可扩展的方式来管理对象的状态和行为。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/183779.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RabbitMQ】 RabbitMQ 消息的延迟 —— 深入探索 RabbitMQ 的死信交换机,消息的 TTL 以及延迟队列

文章目录 一、死信交换机1.1 什么是死信和死信交换机1.2 死信交换机和死信队列的创建方式 二、消息的 TTL2.1 什么是消息的 TTL2.2 基于死信交换机和 TTL 实现消息的延迟 三、基于 DelayExchang 插件实现延迟队列3.1 安装 DelayExchang 插件3.2 DelayExchang 实现消息延迟的原理…

c面向对象编码风格(上)

面向对象和面向过程的基本概念 面向对象和面向过程是两种不同的编程范式,它们在软件开发中用于组织和设计代码的方式。 面向过程编程(Procedural Programming)是一种以过程(函数、方法)为核心的编程方式。在面向过程…

[GXYCTF2019]BabySQli1

提示 与平常的sql注入不同这里需要通过php验证账户密码的 错误逻辑 绕过 尝试万能密码 显示是黑客, 这里对or还有和#以及 都测试一下, 看是过滤了哪个 想试试闭合, 结果直接报错弹出来了, 闭合是 这里需要说的是像这种会报错的在真实情况下都是一般不会出现的, 这个是网…

leetcode:26. 删除有序数组中的重复项(python3解法)

难度:简单 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数…

HTML使用canvas绘制海报(网络图片)

生成前&#xff1a; 生成后&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>媒体参会嘉宾邀请函生成链接</title><link rel"stylesheet" href"https://cdn.jsdelivr.net/npm/vant2.10…

大数据毕业设计选题推荐-家具公司运营数据分析平台-Hadoop-Spark-Hive

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…

Docker:自定义镜像

Docker&#xff1a;自定义镜像 1. 自定义镜像2.实际操作 1. 自定义镜像 我们在通过Dockerfile编写之后&#xff0c;可以通过命令来构建镜像。 2.实际操作 首先&#xff0c;我们将课前资料提供的docker-demo.jar包以及Dockerfile拷贝到虚拟机的/root/demo目录&#xff1a; Do…

【Unity细节】Json序列化时出现:An item with the same key has already been added. Key:

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

MCU常见通信总线串讲(二)—— RS232和RS485

&#x1f64c;秋名山码民的主页 &#x1f602;oi退役选手&#xff0c;Java、大数据、单片机、IoT均有所涉猎&#xff0c;热爱技术&#xff0c;技术无罪 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 获取源码&#xff0c;添加WX 目录 前言一…

软件测试入门之接口测试

首先&#xff0c;什么是接口呢&#xff1f; 接口一般来说有两种&#xff0c;一种是程序内部的接口&#xff0c;一种是系统对外的接口。 系统对外的接口&#xff1a;比如你要从别的网站或服务器上获取资源或信息&#xff0c;别人肯定不会把数据库共享给你&#xff0c;他只能给你…

超详细!DALL · E 文生图模型实践指南

最近需要用到 DALLE的推断功能&#xff0c;在现有开源代码基础上发现还有几个问题需要注意&#xff0c;谨以此篇博客记录之。 我用的源码主要是 https://github.com/borisdayma/dalle-mini 仓库中的Inference pipeline.ipynb 文件。 运行环境&#xff1a;Ubuntu服务器 ⚠️注意…

MVC、MVP、MVVM区别

MVC、MVP、MVVM区别 MVC&#xff08;Model-View-Controller&#xff09; 。是一种设计模式&#xff0c;通常用于组织与应用程序的数据流。它通常包括三个组件&#xff1a;模型&#xff08;Model&#xff09;、视图&#xff08;View&#xff09;和控制器&#xff08;Controller&…

JWT原理分析——JWT

了解为什么会有JWT的出现&#xff1f; 首先不得不提到一个知识叫做跨域身份验证&#xff0c;JWT的出现就是为了更好的解决这个问题&#xff0c;但是在没有JWT的时候&#xff0c;我们一般怎么做呢&#xff1f;一般使用Cookie和Session&#xff0c;流程大体如下所示&#xff1a;…

基于ssm在线考试系统设计与实现(代码+文档+数据库)

ssm在线考试系统&#xff0c;java在线考试系统&#xff0c;在线考试系统 运行环境&#xff1a; JAVA版本&#xff1a;JDK1.8 IDE类型&#xff1a;IDEA、Eclipse都可运行 数据库类型&#xff1a;MySql&#xff08;8.x版本都可&#xff09; 硬件环境&#xff1a;Windows 角色&…

【GitLab CI/CD、SpringBoot、Docker】GitLab CI/CD 部署SpringBoot应用,部署方式Docker

介绍 本文件主要介绍如何将SpringBoot应用使用Docker方式部署&#xff0c;并用Gitlab CI/CD进行构建和部署。 环境准备 已安装Gitlab仓库已安装Gitlab Runner&#xff0c;并已注册到Gitlab和已实现基础的CI/CD使用创建Docker Hub仓库&#xff0c;教程中使用的是阿里云的Docker…

腾讯云16核服务器配置有哪些?CPU型号处理器主频性能

腾讯云16核服务器配置大全&#xff0c;CVM云服务器可选择标准型S6、标准型SA3、计算型C6或标准型S5等&#xff0c;目前标准型S5云服务器有优惠活动&#xff0c;性价比高&#xff0c;计算型C6云服务器16核性能更高&#xff0c;轻量16核32G28M带宽优惠价3468元15个月&#xff0c;…

后端接口接收对象和文件集合,formdata传递数组对象

0 问题 后端接口需要接收前端传递过来的对象和文件集合&#xff1b;对象中存在数组对象 1 前端和后端 前端只能使用formdata来传递参数&#xff0c;后端不使用RequestBody注解 2 formdata传递数组对象 2.1 多个参数对象数组 addForm: {contactInfo: [{contactPerson: ,…

Apifox日常使用(一键本地联调)

背景说明&#xff1a;现在的项目一般都是前后分离&#xff0c;线上出bug或者在进行联调时&#xff0c;有些时候后端需要重复模拟前端数据格式&#xff0c;在使用Apifox的情况下&#xff0c;如何快速造出后端需要的数据呢&#xff1f; 随便找一个网站&#xff0c;点开f12&#…

FastGPT | 3分钟构建属于自己的AI智能助手

这是一篇使用指南&#xff01;&#xff01;&#xff01; FastGPT是什么&#xff1f; FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&…

Solidity快速入门之函数输出

返回值return和returns Solidity有两个关键字与函数输出相关&#xff1a;return和returns&#xff0c;他们的区别在于&#xff1a; returns加在函数名后面&#xff0c;用于声明返回的变量类型及变量名&#xff1b;return用于函数主体中&#xff0c;返回想要返回的变量&#x…