Java设计模式之行为型-状态模式(UML类图+案例分析)

news2024/11/24 11:48:11

目录

一、基础概念

二、UML类图

三、角色设计

四、案例分析

五、总结


一、基础概念

状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。

二、UML类图

三、角色设计

角色描述
环境角色维护一个State的实例,用来表示当前状态。并且定义了状态转换的set方法以及请求处理的业务方法。
抽象状态角色定义一个接口封装与环境角色的一个特点接口相关行为
具体状态角色实现抽象状态接口,封装状态对应的行为

四、案例分析

下面案例通过状态模式模拟了一个电梯系统,电梯在开门、关门、运行、停止不同状态下的行为逻辑,以及在客户端调用之间状态的转变。

定义了电梯状态的抽象接口,包含开门、关门、运行、停止等操作。这对应状态模式中的抽象状态角色:

public interface LiftState {

  /**
   * 设置环境变量
   * @param context
   */
  public void setContext(Context context);

  /**
   * 电梯开启操作
   */
  public void open();

  /**
   * 电梯关闭操作
   */
  public void close();

  /**
   * 电梯运行操作
   */
  public void run();

  /**
   * 电梯停止操作
   */
  public void stop();
}

实现了LiftState接口,作为具体状态类,封装了对应的状态下电梯的行为逻辑。这对应具体状态角色:

public class OpeningState implements LiftState {

    private Context context;

    @Override
    public void setContext(Context context) {
        this.context = context;
    }

    @Override
    public void open() {
        //电梯门是打开的,不操作
        System.out.println("电梯门已打开.....");
    }

    @Override
    public void close() {
        //电梯门打开,可以执行关闭操作
        this.context.setLiftState(Context.CLOSING_STATE);
        this.context.close();
    }

    @Override
    public void run() {
        //电梯开门的时候不可以运行
    }

    @Override
    public void stop() {
        //电梯能开门必须是停下的
    }
}

public class ClosingState implements LiftState {

    private Context context;

    @Override
    public void setContext(Context context) {
        this.context = context;
    }

    @Override
    public void open() {
        //关门状态下可以开门
        this.context.setLiftState(Context.OPENING_STATE);
        this.context.open();
    }
 
    @Override
    public void close() {
        //已经时关门状态,无需关门操作
        System.out.println("电梯门关闭.....");
    }
 
    @Override
    public void run() {
        //关了门自然可以运行
        this.context.setLiftState(Context.RUNNING_STATE);
        this.context.run();
    }
 
    @Override
    public void stop() {
        //电梯门关了但我不按楼层
        this.context.setLiftState(Context.CLOSING_STATE);
        this.context.stop();
    }
}

public class RunningState implements LiftState {

    private Context context;

    @Override
    public void setContext(Context context) {
        this.context = context;
    }

    @Override
    public void open() {
        //运行状态下不可开门
    }
 
    @Override
    public void close() {
        //运行状态下已经是关好门的
    }
 
    @Override
    public void run() {
        //正在运行,无需操作
        System.out.println("电梯正在运行.......");
    }
 
    @Override
    public void stop() {
        //运行可停下
        this.context.setLiftState(Context.STOPPING_STATE);
        this.context.stop();
    }
}

public class StoppingState implements LiftState {

    private Context context;

    @Override
    public void setContext(Context context) {
        this.context = context;
    }

    @Override
    public void open() {
        //停下的状态时可以开门的
        this.context.setLiftState(Context.OPENING_STATE);
        this.context.open();
    }
 
    @Override
    public void close() {
        //停下的时候已经是关了门的
    }
 
    @Override
    public void run() {
        //停下也可以继续运行
        this.context.setLiftState(Context.RUNNING_STATE);
        this.context.run();
    }
 
    @Override
    public void stop() {
        //已经停下无需操作
        System.out.println("电梯停止.......");
    }
}

维护当前状态,提供请求委托和状态切换方法,对应的是环境角色:


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 void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //将当前对象作为参数传递到状态对象的环境中
        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();
    }
}

客户端:

public class Client {

  public static void main(String[] args) {
    Context context = new Context();
    context.setLiftState(new RunningState());
    context.open(); 
    context.close();
    context.run();
    context.stop();
  }

}

运行结果如下:

执行流程如下:

1、Client调用Context的开门、运行接口。

2、Context委托请求给当前的状态对象。

3、状态对象执行状态对应的行为,如果需要转变状态,则调用Context切换状态。

4、Context切换状态变量到新状态,新的状态对象成为当前活动状态5. 新的状态对象接受请求,执行相应行为

综上,状态对象封装行为,Context维护状态,Client只与Context交互,这样将状态转移逻辑聚合到状态对象中,实现了与Context的解耦。

状态模式很好地封装了状态切换逻辑,消除了庞大的条件分支语句,提供了可维护的状态机。 

五、总结

优点:

1、封装了状态转换逻辑,将不同状态的行为局部化,避免Context中的复杂条件分支。

2、方便增加和删除状态,只需要改变状态类即可。

3、可以避免直接修改Context而引入不一致性。

4、提高了Context类的复用性。

缺点:

1、增加了类的数量,复杂度较高。

2、状态切换的条件分散在多个状态类中,不便维护。

3、状态类只能通过Context来间接访问资源,限制性较大。

应用场景

1、一个对象的行为依赖于它的状态,并且它必须在运行时刻根据状态改变它的行为。

2、代码中包含大量与状态有关的条件语句,这些条件语句的作用是根据对象的状态选择不同的行为,这样会导致代码混乱和不易维护。

3、一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。

4、需要管理的状态数量过多,这样就可以将不同的状态表示为不同的类。

5、系统中对象状态转换的规则比较复杂,状态模式可以将状态的转换逻辑集中到一个地方。

符合的设计原则:

1开闭原则(Open Closed Principle)

状态模式可以在不修改对象自身的情况下,在运行时动态切换状态,扩展新的状态类。符合开闭原则的要求,对扩展开放,对修改关闭。

2单一职责原则(Single Responsibility Principle)

每个状态类只负责对象在某一个状态下的行为。状态类之间互不影响,符合单一职责原则。

3里氏替换原则(Liskov Substitution Principle)

子类可以扩展父类状态的行为,而不会破坏父类状态的行为预期。符合里氏替换原则。

4依赖倒置原则(Dependency Inversion Principle)

状态类都依赖于抽象状态类,而不是具体的Context类。符合依赖倒置原则。

5接口隔离原则(Interface Segregation Principle)

抽象状态类提供简单的行为方法,避免客户端需要不必要的接口。符合接口隔离原则。

6组合聚合复用原则(Composite Reuse Principle)

客户端可以统一使用Context类,不需要与不同的具体状态类打交道。符合组合聚合复用原则。

7迪米特法则(Law of Demeter)

Context通过抽象状态类与各具体状态类交互,符合迪米特法则的“只与直接朋友通信”

总之,状态模式通过将复杂状态转换逻辑提取到不同的状态类中,可以提高代码的灵活性和可维护性,是一种比较常用和有效的设计模式。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

有必要买apple pencil吗?ipad pencil替代品

苹果的电容笔与一般的电容笔有什么不同?事实上,从外观上来看,两者并没有太大的区别。唯一不同的是,苹果电容笔的重量更大,笔尖内部有专门的重力感应器,可以感应到重力压感对线条的影响。因此苹果的这个产品…

MongoDB复制集

文章目录 一、介绍1、存在的意义和作用?2、需要实现啥功能?3、典型案例4、注意事项 二、搭建1、安装MongoDB,配置环境变量2、创建数据目录3、配置文件4、启动 MongoDB 进程5、配置复制集6、验证 三、写策略writeConcern1、w参数2、j参数2.1 介…

《如何使用思维导图进行知识结构的建立和扩展》

I.思维导图作为知识管理工具的重要性 思维导图是一款强大的效率工具,可以帮助有效的管理知识。”一图胜千言“它用图形的方式,将各个主题连接起来。我们可以清晰的了解到各主题之间的关系。 在知识管理中,通过创建一个知识主题的中心&#xf…

PVE虚拟化平台之安装Ubuntu-server系统

PVE虚拟化平台之安装Ubuntu-server系统 一、Ubuntu介绍1.1 Ubuntu简介1.2 Ubuntu版本1.3 ubuntu命名规则 二、上传镜像到PVE存储2.1 检查PVE环境2.2 上传镜像 三、创建虚拟机3.1 设置虚拟机名称3.2 操作系统设置3.3 系统设置3.4 磁盘设置3.5 cpu设置3.6 内存设置3.7 网络设置3.…

libvirt 热迁移流程及参数介绍

01 热迁移基本原理 1.1 热迁移概念 热迁移也叫在线迁移,是指虚拟机在开机状态下,且不影响虚拟机内部业务正常运行的情况下,从一台宿主机迁移到另外一台宿主机上的过程。 1.2 虚拟机数据传输预拷贝和后拷贝 预拷贝(pre-copy): …

3、wampserver中查看各项当前版本及简单配置PHP

wampserver点击左键,即可查看Apache,PHP,MySQL,MariaDB的当前版本 在wampserver的安装目录中,在相应的D:\wamp64\bin\php\php8.0.26 php.ini文件中,short_open_tag On(是否允许使用 PHP代码开…

在新建环境下配置低版本opencv

我这边是要解决 python报错:AttributeError: ‘module’ object has no attribute xfeatures2d’的问题, xfeatures2d在新版本已经被取消,但是需要使用老版本的一个函数 确定opencv与python的版本对应关系 一般来说可以对照这个表 具体来说…

Mac下安装python使用TensorFlow训练自己的模型

程序猿日常 Mac下安装python使用TensorFlow训练自己的模型目标 https://www.tensorflow.org/lite/models/modify/model_maker/image_classification?hlzh-cn 安装Python3.8版本 下载地址双击安装 安装pip curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py pyth…

ZKML:区块链世界的AI+隐私

1. 引言 本文主要参考: 2023年6月drCathieSo.eth 与 Ethereum Malaysia 视频 ZKML: Verifiable & Privacy-Preserving Compute in Blockchain2023年6月drCathieSo.eth 与 PSE 视频 Folding Circom circuits: a ZKML case study - Dr. Cathie So ZKML&#xf…

解决SpringMVC中@ResponseBody返回中文乱码

错误 解决方案一 Controller中的注解采用如下方式&#xff1a; GetMapping(value "/init" ,produces "application/json;charsetutf-8")这种方式仅对设置了的方法有效。 解决方案二 在applicationContext.xml中添加如下代码&#xff1a; <!-- 解…

广播与组播

目录 一、广播1. 什么是广播&#xff1f;2. 广播的实现 二、组播1. 分类的IP地址2. 多播IP地址3. 组播的实现 广播与组播和实现UDP通信的代码差不多 一、广播 1. 什么是广播&#xff1f; 数据包发送方式只有一个接受方&#xff0c;称为单播 如果同时发给局域网中的所有主机&…

解决vite+vue3打包部署到非根目录路径问题

修改vite.config.js文件&#xff0c;base为部署路径 base配置选项&#xff1a;

vue3 my-cron-vue3插件的使用

my-cron-vue3 这是一个cron表达式生成插件,基于vue3.0与element-plus实现。 npm i my-cron-vue3//前置配置 import { createApp } from vue import ElementPlus from element-plus; import element-plus/lib/theme-chalk/index.css; import App from ./App.vue //全局引入 imp…

探索华为、思科和瞻博网络的基本ACL和高级ACL配置方法

在网络安全中&#xff0c;访问控制列表&#xff08;Access Control List&#xff0c;简称ACL&#xff09;是一种重要的工具&#xff0c;用于控制数据包在网络中的流动。多家网络设备厂商提供了各自的ACL配置方法&#xff0c;其中华为、思科和瞻博网络是备受认可和使用广泛的品牌…

(中等)LeetCode 剑指OfferII 074. 合并区间

排序&#xff1a; 用数组merged存储最终的答案 首先&#xff0c;将列表中的区间按照左端点升序排序&#xff0c;将第一个区间加入merged数组中&#xff0c;并按顺序依次考虑之后的每个区间&#xff1a; 如果当前区间的左端点在数组merged中最后一个区间的右端点之后&#xf…

ChatGPT提问的万能公式,强烈建议收藏!泰裤辣!

在实际使用GPT的时候&#xff0c;并不是GPT不够强大&#xff0c;而是我们需要很多时间去调教AI&#xff0c;以便输出我们期望的答案&#xff0c;为了让输出无限的靠近你的期望&#xff0c;就需要下面这个万能的框架&#xff0c;如果大家记不住这个框架或者没有形成习惯&#xf…

nginx相关

1、nginx无默认配置文件 参考文章&#xff1a;nginx配置失败&#xff0c;卸载后重装无 nginx.conf文件_haojuntu的博客-CSDN博客 2、nginx更改服务器的端口号 参考文章&#xff1a;https://www.cnblogs.com/chaosfe/p/16123585.html#:~:text%E6%88%91%E4%BB%AC%E6%9F%A5%E7%…

【雕爷学编程】Arduino动手做(149)---MAX9814咪头传感器模块7

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

​LeetCode解法汇总​979. 在二叉树中分配硬币

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 给定一个有 N 个结点的二叉树的根结点 root&#xff0c;树中的每个结点上都对应…

从0到1学习Yalmip工具箱(2)-决策变量进阶

博客中所有内容均来源于自己学习过程中积累的经验以及对yalmip官方文档的翻译&#xff1a;https://yalmip.github.io/tutorials/ 1.决策变量的定义 1.1 sdpvar 上文简单介绍了sdpvar函数的用法&#xff0c;接下来将对其进行详细介绍。复习一下&#xff0c;sdpvar函数的基本语…