状态模式

news/2025/12/10 1:50:31/文章来源:https://www.cnblogs.com/stormeye/p/19328859

状态模式:封装状态变化的行为设计艺术

一、状态模式的定义与核心思想

状态模式(State Pattern) 是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其核心思想是 “将对象的状态封装为独立的状态类,将状态转换逻辑与状态对应的行为分离”—— 通过将不同状态下的行为封装到具体状态类中,让上下文对象(持有状态的对象)在状态变化时,自动切换对应的行为,无需在上下文类中编写大量条件判断语句。

简单来说,状态模式解决了 “对象因状态变化导致行为切换,进而产生大量条件判断” 的问题。例如,电梯的运行状态(停止、上升、下降)、订单的生命周期(待支付、已支付、已发货、已完成)、播放器的状态(播放、暂停、停止)等场景,都适合用状态模式封装状态变化与对应行为。

二、状态模式的角色构成

状态模式通常包含以下 3 个核心角色,各角色职责明确、协作完成状态切换与行为执行:

角色名称 核心职责 示例(电梯运行场景)
上下文(Context) 持有当前状态对象,提供状态切换的接口,对外暴露统一的行为方法(委托给当前状态对象执行) Elevator 类(电梯)
抽象状态(State) 定义所有具体状态的公共接口,声明状态对应的行为方法(如电梯的开门、关门、运行方法) ElevatorState 接口
具体状态(ConcreteState) 实现抽象状态接口,封装该状态下的具体行为,同时可包含状态转换的逻辑(如从 “停止” 状态切换到 “上升” 状态) StoppedState(停止状态)、RunningUpState(上升状态)、RunningDownState(下降状态)

三、状态模式的工作原理与实现

1. 工作流程

  1. 上下文对象初始化时,设置初始状态(如电梯初始状态为 “停止”);

  2. 客户端调用上下文的行为方法(如电梯的 “上升” 方法);

  3. 上下文对象将行为委托给当前持有的状态对象执行;

  4. 具体状态对象执行对应行为,若行为触发状态变化(如电梯从 “停止” 变为 “上升”),则由状态对象通知上下文切换到新状态;

  5. 后续客户端调用上下文行为时,将委托给新的状态对象执行。

2. 基础实现(电梯运行状态管理)

以电梯的核心状态(停止、上升、下降)为例,实现状态模式,封装不同状态下的行为与状态转换:

(1)抽象状态(State)
// 抽象状态:电梯状态接口,定义电梯的核心行为public interface ElevatorState {void openDoor(); // 开门void closeDoor(); // 关门void runUp(); // 上升void runDown(); // 下降void stop(); // 停止}
(2)上下文(Context)
// 上下文:电梯类,持有当前状态,对外暴露统一行为接口public class Elevator {// 定义所有可能的状态(也可通过工厂模式创建,此处简化)public final ElevatorState STOPPED_STATE = new StoppedState(this);public final ElevatorState RUNNING_UP_STATE = new RunningUpState(this);public final ElevatorState RUNNING_DOWN_STATE = new RunningDownState(this);private ElevatorState currentState; // 当前状态// 初始化:电梯默认处于停止状态public Elevator() {this.currentState = STOPPED_STATE;System.out.println("电梯初始状态:停止");}// 状态切换方法:由具体状态对象调用,切换当前状态public void setState(ElevatorState state) {this.currentState = state;}// 对外暴露的行为方法:委托给当前状态执行public void openDoor() {currentState.openDoor();}public void closeDoor() {currentState.closeDoor();}public void runUp() {currentState.runUp();}public void runDown() {currentState.runDown();}public void stop() {currentState.stop();}}
(3)具体状态(ConcreteState)
① 停止状态(StoppedState)
// 具体状态:停止状态,实现停止状态下的行为与状态转换public class StoppedState implements ElevatorState {private Elevator elevator; // 持有上下文引用,用于切换状态public StoppedState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 停止状态下可开门System.out.println("电梯处于停止状态,开门成功~");}@Overridepublic void closeDoor() {// 停止状态下可关门(若已开门)System.out.println("电梯处于停止状态,关门成功~");}@Overridepublic void runUp() {// 停止状态下可切换为上升状态System.out.println("电梯从停止状态开始上升~");elevator.setState(elevator.RUNNING_UP_STATE); // 切换到上升状态}@Overridepublic void runDown() {// 停止状态下可切换为下降状态System.out.println("电梯从停止状态开始下降~");elevator.setState(elevator.RUNNING_DOWN_STATE); // 切换到下降状态}@Overridepublic void stop() {// 已处于停止状态,无需操作System.out.println("电梯已处于停止状态,无需重复停止~");}}
② 上升状态(RunningUpState)
// 具体状态:上升状态,实现上升状态下的行为与状态转换public class RunningUpState implements ElevatorState {private Elevator elevator;public RunningUpState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 上升状态下不可开门(安全限制)System.out.println("电梯正在上升,无法开门!");}@Overridepublic void closeDoor() {// 上升状态下门已关闭,无需操作System.out.println("电梯正在上升,门已关闭~");}@Overridepublic void runUp() {// 已处于上升状态,无需操作System.out.println("电梯正在上升,保持运行~");}@Overridepublic void runDown() {// 上升状态下不可直接切换为下降状态(需先停止)System.out.println("电梯正在上升,无法直接下降!请先停止~");}@Overridepublic void stop() {// 上升状态下可切换为停止状态System.out.println("电梯停止上升,切换为停止状态~");elevator.setState(elevator.STOPPED_STATE); // 切换到停止状态}}
③ 下降状态(RunningDownState)
// 具体状态:下降状态,实现下降状态下的行为与状态转换public class RunningDownState implements ElevatorState {private Elevator elevator;public RunningDownState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 下降状态下不可开门(安全限制)System.out.println("电梯正在下降,无法开门!");}@Overridepublic void closeDoor() {// 下降状态下门已关闭,无需操作System.out.println("电梯正在下降,门已关闭~");}@Overridepublic void runUp() {// 下降状态下不可直接切换为上升状态(需先停止)System.out.println("电梯正在下降,无法直接上升!请先停止~");}@Overridepublic void runDown() {// 已处于下降状态,无需操作System.out.println("电梯正在下降,保持运行~");}@Overridepublic void stop() {// 下降状态下可切换为停止状态System.out.println("电梯停止下降,切换为停止状态~");elevator.setState(elevator.STOPPED_STATE); // 切换到停止状态}}
(4)客户端测试
public class Client {public static void main(String[] args) {// 创建上下文对象(电梯)Elevator elevator = new Elevator();System.out.println("n1. 测试停止状态下的操作:");elevator.openDoor(); // 停止状态可开门elevator.closeDoor(); // 停止状态可关门elevator.runUp(); // 停止状态切换为上升System.out.println("n2. 测试上升状态下的操作:");elevator.openDoor(); // 上升状态不可开门elevator.runUp(); // 上升状态保持运行elevator.stop(); // 上升状态切换为停止System.out.println("n3. 测试停止状态切换为下降:");elevator.runDown(); // 停止状态切换为下降System.out.println("n4. 测试下降状态下的操作:");elevator.runUp(); // 下降状态不可直接上升elevator.stop(); // 下降状态切换为停止}}
输出结果
电梯初始状态:停止1. 测试停止状态下的操作:电梯处于停止状态,开门成功~电梯处于停止状态,关门成功~电梯从停止状态开始上升~2. 测试上升状态下的操作:电梯正在上升,无法开门!电梯正在上升,保持运行~电梯停止上升,切换为停止状态~3. 测试停止状态切换为下降:电梯从停止状态开始下降~4. 测试下降状态下的操作:电梯正在下降,无法直接上升!请先停止~电梯停止下降,切换为停止状态~

3. 扩展:状态模式的灵活扩展(新增 “故障状态”)

状态模式的核心优势是易于扩展新状态,无需修改原有状态类和上下文类,只需新增具体状态类并实现抽象状态接口:

(1)新增具体状态:故障状态(FaultState)
// 新增具体状态:故障状态public class FaultState implements ElevatorState {private Elevator elevator;public FaultState(Elevator elevator) {this.elevator = elevator;}@Overridepublic void openDoor() {// 故障状态下仅允许手动开门(紧急情况)System.out.println("电梯处于故障状态,紧急开门成功(仅手动操作)~");}@Overridepublic void closeDoor() {System.out.println("电梯处于故障状态,关门成功~");}@Overridepublic void runUp() {System.out.println("电梯处于故障状态,无法上升!");}@Overridepublic void runDown() {System.out.println("电梯处于故障状态,无法下降!");}@Overridepublic void stop() {// 故障状态下停止操作无效(已停止运行)System.out.println("电梯处于故障状态,已停止运行~");}}
(2)上下文扩展(添加故障状态)
public class Elevator {// 新增故障状态public final ElevatorState FAULT_STATE = new FaultState(this);// 其他代码不变...// 新增故障触发方法(对外暴露)public void triggerFault() {System.out.println("n触发电梯故障!");this.setState(FAULT_STATE);}}
(3)扩展客户端测试
public class Client {public static void main(String[] args) {Elevator elevator = new Elevator();System.out.println("n5. 测试故障状态:");elevator.triggerFault(); // 触发故障elevator.openDoor(); // 故障状态紧急开门elevator.runUp(); // 故障状态无法上升elevator.stop(); // 故障状态已停止}}
扩展输出结果
5. 测试故障状态:触发电梯故障!电梯处于故障状态,紧急开门成功(仅手动操作)~电梯处于故障状态,无法上升!电梯处于故障状态,已停止运行~

四、状态模式的应用场景

状态模式适用于以下场景:

  1. 对象行为随状态变化而变化:例如,订单状态(待支付→已支付→已发货→已完成),不同状态下的订单支持的操作(取消订单、退款、确认收货)不同。

  2. 避免大量条件判断语句:若上下文类中存在大量 if-elseswitch-case 语句判断状态并执行不同行为,适合用状态模式重构(将条件判断转换为状态类的多态调用)。

  3. 状态转换逻辑复杂:状态之间的转换规则较多,且转换逻辑与状态行为紧密相关(如电梯的状态转换受安全规则限制)。

  4. 需要动态切换状态:对象在运行时需根据外部事件动态切换状态,且切换后行为需自动调整(如播放器的播放 / 暂停 / 停止状态切换)。

五、状态模式的优缺点

优点

  1. 封装状态与行为:将不同状态下的行为封装到独立的状态类中,符合 “单一职责原则”,代码结构清晰。

  2. 消除条件判断:避免上下文类中大量的条件判断语句,通过状态类的多态调用实现行为切换,提高代码可维护性。

  3. 易于扩展新状态:新增状态只需实现抽象状态接口,无需修改原有代码,符合 “开闭原则”(如电梯新增故障状态无需改动原有状态类)。

  4. 状态转换逻辑清晰:状态转换规则集中在具体状态类中,便于理解和维护。

缺点

  1. 类数量增多:每个状态对应一个具体状态类,若状态数量较多(如订单有 10 种状态),会导致系统中类的数量激增,增加开发和维护成本。

  2. 状态类与上下文耦合:具体状态类持有上下文引用(用于切换状态),导致状态类与上下文存在一定耦合(可通过引入状态管理器降低耦合)。

  3. 简单状态场景冗余:若对象只有少数几种状态,且状态转换逻辑简单,使用状态模式会增加不必要的复杂性(直接用条件判断更简洁)。

六、状态模式与命令模式的核心区别

状态模式与命令模式均为行为型设计模式,且都通过封装实现解耦,但核心目标和应用场景差异显著:

维度 状态模式 命令模式
核心目标 封装状态变化与状态对应的行为 封装请求,解耦发送者与接收者
角色关系 上下文持有状态,状态依赖上下文(双向关联) 调用者持有命令,命令持有接收者(单向关联)
行为触发 行为由当前状态决定(状态驱动) 行为由命令对象决定(请求驱动)
状态 / 命令复用 状态可在多个上下文间复用 命令可在多个调用者间复用
典型场景 电梯状态、订单状态、播放器状态 遥控器操作、撤销 / 重做、命令队列

简单来说:状态模式是 “状态决定行为”,命令模式是 “请求触发行为”—— 状态模式聚焦对象自身的状态变化,命令模式聚焦请求的发送与执行分离。

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

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

相关文章

每日3题 4(咕咕咕

孩子们事情太多了明天再说

基于事件驱动机制的提醒系统设计方案

基于事件驱动机制的提醒系统设计方案一、系统架构设计 采用 "分层架构 + 事件驱动引擎" 模式,各层级职责与技术选型如下:层级核心职责技术选型前端层用户交互(增删改查提醒)、实时接收提醒、处理确认操作…

docker快速上手

docker快速上手 安装配置# 移除旧版本 sudo dnf remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine # 配置yum源 sudo …

全球首个液冷迷你机!abee AI Station 395 Max工作站图赏

全球首个液冷迷你机!abee AI Station 395 Max工作站图赏Posted on 2025-12-10 01:22 lzhdim 阅读(0) 评论(0) 收藏 举报日前,abee AI Station 395 Max迷你液冷AI工作站发布,定价为17999元。 现在这款新品已经来…

Docker环境下Redis ACL实战踩坑记:权限、挂载与用户配置解析

Docker环境下Redis ACL实战踩坑指南:权限、挂载与用户配置解析 开篇:实战场景说明 最近在生产环境中部署Redis时,我们决定升级到Redis 7.0并启用ACL功能,以实现更精细化的权限控制。核心需求很简单:启用ACL功能,…

一例罗技M275鼠标空键程处理

笔者手中有一个罗技M275鼠标,最近发现鼠标空键程现象比较严重,不如手中另一只M215鼠标手感清脆、爽利。因此笔者尝试解决手中鼠标的空键程现象。 首先根据网络上已经提出的解决办法,打开鼠标,在鼠标左键和微动的接…

Alientech KESS V3: Master Bench-Boot Protocols Activation for Agri Trucks Buses

Overcoming Agriculture Truck & Bus Diagnostic Challenges: The Alientech KESS V3 KESS3 Master Solution In the world of agriculture truck and bus maintenance, downtime is a costly enemy. For European…

洛谷U640022 找割点 题解 点双连通分量

题目链接:https://www.luogu.com.cn/problem/U640022根节点要分割出至少 \(2\) 个连通块(因为根节点没有父节点那部分的连通块)。 其它节点只需要分割出至少 \(1\) 个连通块即可。对于一个 当前节点 \(u\),dfs 它的…

Alientech KESS V3 Master OBD Protocol Activation: Bike, ATV, UTV – Boost Repair Diagnostics

Mastering Protocol Activation for Bikes, ATVs, and UTVs: The Alientech KESS V3 Advantage Problem: Activation Challenges with Specialized Vehicles For European and American automotive professionals and …

第50天(中等题 数据结构)

打卡第五十天 2道中等题题目:思路:代码: class Solution { public:int numOfSubarrays(vector<int>& arr) {const int MODULO = 1000000007; int odd = 0, even = 1; // odd: 奇数前缀数量,ev…

Alientech KESS3 Master: Efficient OBD Protocols Activation for Agri Trucks Buses

Diagnosing the Challenges of Agricultural, Truck, and Bus Diagnostics For automotive professionals and owners working with agriculture, trucks, and buses, traditional diagnostic tools often fall short.…

内网环境-centos7.6配置chrom和flask项目

1.离线安装Anaconda, 配置python环境 #ip网络配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33Linux安装Anaconda教程 2.离线安装chrome 2.1离线安装命令#使用 rpm命令强制安装 --nodeps选项会跳过依赖检查 r…

selenium其他重要的Api

元素等待方法 认识等待 由于⽹络或其他原因,元素未能及时加载出来,⽽代码已执⾏,会触发异常,故要设置等待。代码执⾏过程中,第⼀次未找到元素,先不抛出异常。激活等待时间,在等待过程中如果找到元素就执⾏。 隐…

洛谷P2860 [USACO06JAN] Redundant Paths G 题解 边双连通分量

题目链接:https://www.luogu.com.cn/problem/P2860 解题思路: 双连通分量缩点,设缩点后有 \(cnt\) 个度数为 \(1\) 的点。 则答案为 \(\lceil \frac{cnt}{2} \rceil\)(即 (cnt + 1) / 2)。 示例程序: #include &…

AI真好玩系列-免费解锁 Google Gemini 的几种方式

@目录🏢 1. Gemini Enterprise (企业版)💎 2. Google AI Studio (隐藏宝藏)🤖 3. Google Gemini (个人版)Conclusion | 结语 宝子们来啦!👋 听说你想体验 Google 最强的 AI,这里有一份超简单的「免费试用」攻…

# sg.计算器

# sg.计算器import PySimpleGUI as sg progress = sg.ProgressBar(100) num1_inp = sg.Input(size=5) num2_inp = sg.Input(size=5) result_txt = sg.Text() add_btn = sg.Button(add)layout=[[num1_inp,num2_inp,resu…

智能猫砂盆方案商权威推荐:技术驱动宠物养护新体验 - 星报

清晨的阳光透过窗帘洒进客厅,你手机收到一条推送:“猫咪今晨排泄物pH值正常,但饮水次数比平日减少15%”,在猫咪表现出任何不适症状前,健康预警已经悄然启动。 随着“它经济”的蓬勃发展,全球宠物产业预计到2030年…

网络线序问题了解

今天复习了一些内容,并且知道了双绞线分为直通线和交叉线。并且要按照一定的顺序打线,因为双绞线互相缠绕是用来抗干扰的如果胡乱接会导致电池干扰文件传输FTP: TCP 20 (Data), 21 (Control) SFTP / SSH: TCP 22 TFT…

洛谷U640024 找割边 题解

题目链接:https://www.luogu.com.cn/problem/U640024 边双连通分量 求 割边(桥) 模板题。 示例程序: #include <bits/stdc++.h> using namespace std; const int maxn = 1e4 + 5, maxm = 1e5 + 5;struct Edg…

Python 学习笔记(01)

Python 学习笔记 一、python 基础知识 1.1、Python 语句的缩进 Python代码块使用缩进对齐表示代码逻辑,而不是使用大括号。 # 这是注释,Python的注释使用#表示 print("Hello,World!") # 打印关于缩进的用…