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

【设计模式】【行为型模式】状态模式(State)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

  • 一、入门
    • 什么是状态模式?
    • 为什么需要状态模式?
    • 怎样实现状态模式?
  • 二、状态模式在源码中的运用
    • Java线程管理
    • Spring 状态机(Spring State Machine)
  • 三、总结
    • 状态模式的优点
    • 状态模式的缺点
    • 状态模式的使用场景
  • 参考

一、入门

什么是状态模式?

状态模式(State Pattern)是一种行为设计模式,允许对象在其内部状态改变时改变其行为,使其看起来像是改变了类。状态模式的核心思想是将对象的状态封装成独立的类,并将行为委托给代表当前状态的对象。

为什么需要状态模式?

有这样一个场景,如果不用状态模式,那么电题的每一个动作(open、close、stop、run),都会先对状态进行判断,再决定能否执行(比如运行中的电梯是不能开门的),类图如下所示。
在这里插入图片描述

public class Lift implements ILift {private int state;@Overridepublic void setState(int state) {this.state = state;}//执行关门动作@Overridepublic 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;}}//执行开门动作@Overridepublic void open() {switch (this.state) {case OPENING_STATE://门已经开了,不能再开门了//do nothingbreak;case CLOSING_STATE://关门状态,门打开:System.out.println("电梯门打开了。。。");this.setState(OPENING_STATE);break;case RUNNING_STATE://do nothing 运行时电梯不能开门break;case STOPPING_STATE:System.out.println("电梯门开了。。。");//电梯停了,可以开门了this.setState(OPENING_STATE);break;}}//执行运行动作@Overridepublic 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;}}//执行停止动作@Overridepublic 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;}}
}

在没有使用状态模式的情况下,通常会使用大量的if-elseswitch-case语句来处理对象在不同状态下的行为。这种方式会带来以下问题:

  • 代码臃肿且难以维护:当状态数量增加时,if-elseswitch-case语句会变得非常冗长。每次新增状态或修改状态转换逻辑时,都需要修改这些条件语句,容易引入错误。
  • 违反开闭原则:开闭原则要求软件实体(类、模块、函数等)对扩展开放,对修改关闭。使用条件语句时,新增状态或修改行为需要修改现有代码,而不是扩展。
  • 状态和行为耦合:状态和行为逻辑通常混杂在一起,导致代码难以理解和测试。如果需要复用某些状态的行为,很难将其提取出来。
  • 难以扩展:当状态转换逻辑变得复杂时(例如,某些状态的下一个状态有多个分支),条件语句会变得更加混乱。新增状态或修改状态转换逻辑时,可能会影响其他状态的逻辑。

怎样实现状态模式?

状态模式的组成

  • Context(上下文):它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
  • State(状态):定义一个接口,封装与Context的特定状态相关的行为。
  • ConcreteState(具体状态):实现State接口,定义与Context的某个状态相关的行为。

【案例】 电梯状态改进版
在这里插入图片描述
State(状态)LiftState

//抽象状态类
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();
}

ConcreteState(具体状态)OpenningState类、RunningState类、StoppingState类和ClosingState

//开启状态
public class OpenningState extends LiftState {//开启当然可以关闭了,我就想测试一下电梯门开关功能@Overridepublic void open() {System.out.println("电梯门开启...");}@Overridepublic void close() {//状态修改super.context.setLiftState(Context.closeingState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().close();}//电梯门不能开着就跑,这里什么也不做@Overridepublic void run() {//do nothing}//开门状态已经是停止的了@Overridepublic void stop() {//do nothing}
}//运行状态
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.stoppingState);super.context.stop();}
}//停止状态
public class StoppingState extends LiftState {//停止状态,开门,那是要的!@Overridepublic void open() {//状态修改super.context.setLiftState(Context.openningState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().open();}@Overridepublic void close() {//虽然可以关门,但这个动作不归我执行//状态修改super.context.setLiftState(Context.closeingState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().close();}//停止状态再跑起来,正常的很@Overridepublic void run() {//状态修改super.context.setLiftState(Context.runningState);//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作super.context.getLiftState().run();}//停止状态是怎么发生的呢?当然是停止方法执行了@Overridepublic void stop() {System.out.println("电梯停止了...");}
}//关闭状态
public class ClosingState extends LiftState {@Override//电梯门关闭,这是关闭状态要实现的动作public void close() {System.out.println("电梯门关闭...");}//电梯门关了再打开,逗你玩呢,那这个允许呀@Overridepublic void open() {super.context.setLiftState(Context.openningState);super.context.open();}//电梯门关了就跑,这是再正常不过了@Overridepublic void run() {super.context.setLiftState(Context.runningState);super.context.run();}//电梯门关着,我就不按楼层@Overridepublic void stop() {super.context.setLiftState(Context.stoppingState);super.context.stop();}
}

Context(上下文)Context

//环境角色
public class Context {//定义出所有的电梯状态public final static OpenningState openningState = new OpenningState();//开门状态,这时候电梯只能关闭public final static ClosingState closeingState = new ClosingState();//关闭状态,这时候电梯可以运行、停止和开门public final static RunningState runningState = new RunningState();//运行状态,这时候电梯只能停止public final static StoppingState stoppingState = new StoppingState();//停止状态,这时候电梯可以开门、运行//定义一个当前电梯状态private LiftState liftState;public LiftState getLiftState() {return this.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 ClosingState());context.open();context.close();context.run();context.stop();}
}

二、状态模式在源码中的运用

Java线程管理

Java 的线程(Thread)生命周期就是一个典型的状态模式应用。线程的状态包括NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED虽然 Java 线程的状态管理没有直接使用状态模式的设计,但其设计思想与状态模式非常相似。
线程状态转换:

  • NEW -> RUNNABLE(调用 start() 方法)
  • RUNNABLE -> BLOCKED(等待锁)
  • RUNNABLE -> WAITING(调用 wait() 方法)
  • RUNNABLE -> TIMED_WAITING(调用 sleep()join() 方法)
  • RUNNABLE -> TERMINATED(线程执行完毕)

如果使用状态模式来实现线程状态管理,可以这样设计:

interface ThreadState {void start(ThreadContext context);void run(ThreadContext context);void block(ThreadContext context);void terminate(ThreadContext context);
}class NewState implements ThreadState {@Overridepublic void start(ThreadContext context) {context.setState(new RunnableState());}@Overridepublic void run(ThreadContext context) {throw new IllegalStateException("线程未启动,无法运行。");}@Overridepublic void block(ThreadContext context) {throw new IllegalStateException("线程未启动,无法阻塞。");}@Overridepublic void terminate(ThreadContext context) {throw new IllegalStateException("线程未启动,无法终止。");}
}class RunnableState implements ThreadState {@Overridepublic void start(ThreadContext context) {throw new IllegalStateException("线程已启动,无需再次启动。");}@Overridepublic void run(ThreadContext context) {System.out.println("线程正在运行...");}@Overridepublic void block(ThreadContext context) {context.setState(new BlockedState());}@Overridepublic void terminate(ThreadContext context) {context.setState(new TerminatedState());}
}class ThreadContext {private ThreadState state;public ThreadContext() {this.state = new NewState();}public void setState(ThreadState state) {this.state = state;}public void start() {state.start(this);}public void run() {state.run(this);}public void block() {state.block(this);}public void terminate() {state.terminate(this);}
}

Spring 状态机(Spring State Machine)

Spring 提供了一个状态机框架(Spring State Machine),用于管理复杂的状态转换逻辑。它基于状态模式设计,支持状态、事件、转换等概念。

@Configuration
@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter<String, String> {@Overridepublic void configure(StateMachineStateConfigurer<String, String> states) throws Exception {states.withStates().initial("SI") // 初始状态.state("S1")  // 状态 S1.state("S2");  // 状态 S2}@Overridepublic void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {transitions.withExternal().source("SI").target("S1").event("E1") // 事件 E1 触发 SI -> S1.and().withExternal().source("S1").target("S2").event("E2"); // 事件 E2 触发 S1 -> S2}
}

在这个例子中,Spring State Machine 使用状态模式来管理状态和事件驱动的转换。

三、总结

状态模式的优点

  • 消除复杂的条件语句:状态模式将状态相关的行为分散到各个状态类中,避免了大量的if-elseswitch-case语句,使代码更加清晰。
  • 符合开闭原则:新增状态时,只需添加新的状态类,而无需修改现有代码。修改某个状态的行为时,只需修改对应的状态类,不会影响其他状态。
  • 提高代码的可读性和可维护性:状态和行为被封装在独立的类中,代码结构更加清晰。每个状态类的职责单一,易于理解和测试。
  • 简化上下文逻辑:上下文(Context)只需维护当前状态的引用,并将行为委托给状态对象,逻辑更加简洁。
  • 支持复杂的状态转换:状态模式可以轻松处理多分支的状态转换逻辑(例如,一个状态的下一个状态有多个可能)。

状态模式的缺点

  • 增加类的数量:每个状态都需要一个单独的类,可能会导致类的数量增加,代码结构变得复杂。
  • 状态转换逻辑分散:状态转换逻辑分散在各个状态类中,可能会导致状态转换规则不够直观。
  • 不适合简单的状态管理:如果状态数量很少且状态转换逻辑简单,使用状态模式可能会显得过度设计。

状态模式的使用场景

  • 对象的行为依赖于其状态,且状态数量较多:例如,订单系统(新建、已支付、已发货、已完成等状态)。
  • 状态转换逻辑复杂:例如,游戏角色的状态(站立、跑步、跳跃、攻击等),每个状态的行为和转换条件不同。
  • 需要避免大量的条件语句:例如,线程状态管理(新建、运行、阻塞、终止等),使用状态模式可以避免复杂的条件判断。
  • 状态和行为需要动态切换:例如,TCP 连接状态(建立连接、数据传输、关闭连接等),状态模式可以灵活地管理状态转换。
  • 需要支持分层状态或子状态:例如,工作流引擎中的状态(主状态和子状态),状态模式可以很好地支持分层状态管理。

参考

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili


http://www.mrgr.cn/news/90579.html

相关文章:

  • 性格测评小程序04题库管理
  • Vue.js 状态管理库Pinia
  • Vue笔记(九)
  • 【Stable Diffusion模型测试】测试ControlNet,没有线稿图?
  • 【Mastering Vim 2_02】第一章:Vim 基础入门 + 最新版 Vim 9.1 在 WSL Ubuntu 系统的安装实战
  • 【MySQL】# Windows下安装(压缩包、msi);Linux下安装(压缩包、命令、docker);设置连接数、binlog日志;修改时区
  • Redis7.0八种数据结构底层原理
  • Spring Boot + ShardingSphere 踩坑记
  • 前缀树算法篇:前缀信息的巧妙获取
  • 动态规划LeetCode-416.分割等和子集
  • 动态规划LeetCode-1049.最后一块石头的重量Ⅱ
  • 计算机网络和操作系统常见面试题目(带脑图,做了延伸以防面试官深入提问)
  • 小白零基础如何搭建CNN
  • UGUI Canvas为Overlay模式下的UI元素的position和localPosition
  • 【Matlab算法】基于人工势场的多机器人协同运动与避障算法研究(附MATLAB完整代码)
  • C++病毒(^_^|)(2)
  • 变化检测相关论文可读list
  • 位运算算法篇:异或运算
  • 2、k8s 二进制安装(详细)
  • Ubuntu 22.04 - OpenLDAP安装使用(服务器+LAM+客户端)
  • Golang学习历程【第七篇 闭包type defer panic recover了解time包】
  • 【Unity3D】Jenkins Pipeline流水线自动构建Apk
  • Java 大视界 -- 区块链赋能 Java 大数据:数据可信与价值流转(84)
  • Nginx 中的HTTP2
  • hbase快照同步到目标集群出现ERROR Multiple regions have the same startkey问题分析
  • 【Qt 常用控件】多元素控件(QListWidget、QTableWidgt、QTreeWidget)