【设计模式】状态模式 (状态对象(Objects for States))

状态模式(State Pattern)详解


一、状态模式简介

状态模式(State Pattern) 是一种 行为型设计模式(对象行为型模式),它允许一个对象在其内部状态改变时改变其行为。换句话说,对象看起来好像修改了它的类。

你可以这样理解:

“同一个对象,在不同状态下表现出不同的行为。”

这就像一个自动售货机:

  • 当你没有投币时,它不响应“出货”按钮;
  • 投币后,它才允许你选择商品;
  • 选择商品后,它才会出货并找零。

这个机器的行为随着内部状态(如“未投币”、“已投币”、“已选择”)的变化而变化。

  • 红绿灯
    在这里插入图片描述

  • H2O的三种状态(未考虑临界点)

在这里插入图片描述

在软件系统中
有些对象具有多种状态。
这些状态在某些情况下能够相互转换。
对象在不同的状态下将具有不同的行为。
复杂的条件判断语句来进行状态的判断和转换操作 --> 导致代码的可维护性和灵活性下降 --> 出现新的状态时,代码的扩展性很差,客户端代码也需要进行相应的修改,违背了开闭原则。

// 非常多的if else
class TestXYZ 
{int behaviour;//Getter and Setter......public void HandleAll(){if (behaviour == 0){ //do something }else if (behaviour == 1){ //do something }else if (behaviour == 2){ //do something }else if (behaviour == 3){ //do something }... some more else if ...}
}

又名状态对象(Objects for States)
用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。
将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。
对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。

状态模式包含以下3个角色
Context(环境类)
State(抽象状态类)
ConcreteState(具体状态类)

在这里插入图片描述


二、解决的问题类型

状态模式主要用于解决以下问题:

  • 对象的行为依赖于其状态,并且状态较多导致代码中出现大量 if-elseswitch-case 判断逻辑
  • 希望将每种状态的行为封装到独立的类中,提高可读性和可维护性
  • 需要在运行时动态切换对象的行为

三、使用场景

场景示例
工作流/审批系统订单状态:待支付 → 已支付 → 发货中 → 已完成
游戏角色控制角色状态:站立、奔跑、跳跃、死亡等
用户登录状态未登录、已登录、锁定、超时等
电梯控制系统运行、停止、开门、关门等状态

在有些情况下,多个环境对象可能需要共享同一个状态。
如果希望在系统中实现多个环境对象共享一个或多个状态对象,那么需要将这些状态对象定义为环境类的静态成员对象。

对于客户端而言,无须关心状态类,可以为环境类设置默认的状态类,将状态的转换工作交给环境类(或具体状态类)来完成,具体的转换细节对于客户端而言是透明的。
可以通过环境类来实现状态转换,环境类作为一个状态管理器,统一实现各种状态之间的转换操作。

对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。


四、核心概念

  1. Context(上下文):持有当前状态对象的引用,对外提供接口。
  2. State(状态接口):定义所有具体状态类必须实现的行为。
  3. ConcreteState(具体状态类):实现状态接口,封装特定状态下的行为。

五、实际代码案例(Java)

我们以一个简单的“订单”系统为例,演示状态模式的使用。

1. 定义状态接口

// 订单状态接口
interface OrderState {void pay(OrderContext context);void ship(OrderContext context);void deliver(OrderContext context);void cancel(OrderContext context);
}

2. 创建具体状态类

// 待支付状态
class PendingState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("订单已支付,准备发货...");context.setState(new ShippedState()); // 转换到已发货状态}@Overridepublic void ship(OrderContext context) {System.out.println("请先支付!");}@Overridepublic void deliver(OrderContext context) {System.out.println("请先支付并发货!");}@Overridepublic void cancel(OrderContext context) {System.out.println("订单已取消。");context.setState(new CancelledState());}
}// 已发货状态
class ShippedState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("订单已支付过,无需重复支付。");}@Overridepublic void ship(OrderContext context) {System.out.println("订单已经在运输途中。");}@Overridepublic void deliver(OrderContext context) {System.out.println("货物已送达!");context.setState(new DeliveredState());}@Overridepublic void cancel(OrderContext context) {System.out.println("发货后无法取消订单。");}
}// 已送达状态
class DeliveredState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("订单已完成,无需支付。");}@Overridepublic void ship(OrderContext context) {System.out.println("订单已送达,无需发货。");}@Overridepublic void deliver(OrderContext context) {System.out.println("订单已送达,请勿重复操作。");}@Overridepublic void cancel(OrderContext context) {System.out.println("订单已完成,无法取消。");}
}// 已取消状态
class CancelledState implements OrderState {@Overridepublic void pay(OrderContext context) {System.out.println("订单已取消,无法支付。");}@Overridepublic void ship(OrderContext context) {System.out.println("订单已取消,无法发货。");}@Overridepublic void deliver(OrderContext context) {System.out.println("订单已取消,无法送达。");}@Overridepublic void cancel(OrderContext context) {System.out.println("订单已取消。");}
}

3. 创建上下文类(订单)

// 订单上下文
class OrderContext {private OrderState state;public OrderContext() {this.state = new PendingState(); // 初始状态:待支付}public void setState(OrderState state) {this.state = state;}// 委托给当前状态对象处理public void pay() {state.pay(this);}public void ship() {state.ship(this);}public void deliver() {state.deliver(this);}public void cancel() {state.cancel(this);}
}

4. 客户端测试类

public class Client {public static void main(String[] args) {OrderContext order = new OrderContext();System.out.println("=== 模拟订单流程 ===");order.pay();     // 支付order.ship();    // 发货(此时无实际发货动作,仅提示)order.deliver(); // 送达order.cancel();  // 尝试取消System.out.println("\n=== 尝试取消未支付订单 ===");OrderContext order2 = new OrderContext();order2.cancel();}
}

输出结果:

=== 模拟订单流程 ===
订单已支付,准备发货...
订单已经在运输途中。
货物已送达!
订单已完成,无法取消。=== 尝试取消未支付订单 ===
订单已取消。

典型代码案例:

典型的抽象状态类代码

abstract class State
{//声明抽象业务方法,不同的具体状态类可以有不同的实现public abstract void Handle();
}

典型的环境类代码

class Context
{private State state; //维持一个对抽象状态对象的引用private int value; //其他属性值,该属性值的变化可能会导致对象状态发生变化//设置状态对象public void SetState(State state) 
{this.state = state;}public void Request() 
{//其他代码state.Handle(); //调用状态对象的业务方法//其他代码}
}

状态转换的实现

  1. 统一由环境类来负责状态之间的转换,环境类充当了状态管理器(State Manager)角色。
     ……
public void ChangeState()
{//判断属性值,根据属性值进行状态转换
if (value == 0)
{this.SetState(new ConcreteStateA());}else if (value == 1)
{this.SetState(new ConcreteStateB());}......}……
  1. 由具体状态类来负责状态之间的转换,可以在具体状态类的业务方法中判断环境类的某些属性值,再根据情况为环境类设置新的状态对象,实现状态转换。
 ……
public void ChangeState(Context ctx) 
{//根据环境对象中的属性值进行状态转换
if (ctx.Value == 1) 
{ctx.SetState(new ConcreteStateB());}else if (ctx.Value == 2) 
{ctx.SetState(new ConcreteStateC());}......}……

其他案例:

  1. 某软件公司要为一银行开发一套信用卡业务系统,银行账户(Account)是该系统的核心类之一,通过分析,该软件公司开发人员发现在系统中账户存在3种状态,且在不同状态下账户存在不同的行为,具体说明如下:
    (1) 如果账户中余额大于等于0,则账户的状态为正常状态(Normal State),此时用户既可以向该账户存款也可以从该账户取款;
    (2) 如果账户中余额小于0,并且大于-2000,则账户的状态为透支状态(Overdraft State),此时用户既可以向该账户存款也可以从该账户取款,但需要按天计算利息;
    (3) 如果账户中余额等于-2000,那么账户的状态为受限状态(Restricted State),此时用户只能向该账户存款,不能再从中取款,同时也将按天计算利息;
    (4) 根据余额的不同,以上3种状态可发生相互转换。
    现使用状态模式设计并实现银行账户状态的转换。

在这里插入图片描述

在这里插入图片描述

  1. 论坛用户等级
    在某论坛系统中,用户可以发表留言,发表留言将增加积分;用户也可以回复留言,回复留言也将增加积分;用户还可以下载文件,下载文件将扣除积分。该系统用户分为三个等级,分别是新手、高手和专家,这三个等级对应三种不同的状态,这三种状态分别定义如下:
    (1) 如果积分小于100分,则为新手状态,用户可以发表留言、回复留言,但是不能下载文件。如果积分大于等于1000分,则转换为专家状态;如果积分大于等于100分,则转换为高手状态。
    (2) 如果积分大于等于100分但小于1000分,则为高手状态,用户可以发表留言、回复留言,还可以下载文件,而且用户在发表留言时可以获取双倍积分。如果积分小于100分,则转换为新手状态;如果积分大于等于1000分,则转换为专家状态;如果下载文件后积分小于0,则不能下载该文件。
    (3) 如果积分大于等于1000分,则为专家状态,用户可以发表留言、回复留言和下载文件,用户除了在发表留言时可以获取双倍积分外,下载文件只扣除所需积分的一半。如果积分小于100分,则转换为新手状态;如果积分小于1000分,但大于等于100,则转换为高手状态;如果下载文件后积分小于0,则不能下载该文件。

在这里插入图片描述

  1. 某系统要求两个开关对象要么都处于开的状态,要么都处于关的状态,在使用时它们的状态必须保持一致,开关可以由开转换到关,也可以由关转换到开。
    试使用状态模式来实现开关的设计。

在这里插入图片描述

  1. 现要开发一个屏幕放大镜工具,其具体功能描述如下:
    用户单击“放大镜”按钮之后屏幕将放大一倍,再单击一次“放大镜”按钮屏幕再放大一倍,第三次单击该按钮后屏幕将还原到默认大小。
    试使用状态模式来设计该屏幕放大镜工具。

在这里插入图片描述


六、优缺点分析

优点描述
消除复杂的条件判断语句if-elseswitch 分散到各个状态类中,结构清晰。允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
符合开闭原则新增状态只需添加新类,无需修改现有代码
职责分离每个状态类只关注自己状态下的行为,职责明确
其他可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。封装了状态的转换规则,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
缺点描述
增加类的数量每个状态对应一个类,可能导致类膨胀
状态转换逻辑分散状态之间的流转由具体状态类控制,可能不够集中。对开闭原则的支持并不太好。增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码
不适合状态较少的场景如果状态只有两三种,使用状态模式反而显得过度设计。结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度

七、与策略模式对比(补充)

对比点状态模式策略模式
目的改变对象的行为以反映其状态变化封装算法,使算法可互换
状态流转状态之间可以相互转换策略之间通常是独立的
上下文关系上下文行为随状态变化而变上下文使用策略完成某项任务

八、最终小结

状态模式是一种非常实用的设计模式,特别适用于那些具有多个状态、且每个状态下行为差异较大的对象。它通过将每种状态封装成独立的类,使程序结构更清晰、易于扩展和维护。

在开发订单系统、工作流引擎、游戏状态管理、UI 控件状态控制等项目中,状态模式能有效提升代码的可读性和可维护性。


📌 一句话总结:

状态模式就像一个“状态驱动的行为控制器”,让对象根据自己的“心情”(状态)做出不同的反应。


推荐使用场景:

  • 对象有多个状态,且状态转换频繁;
  • 使用大量 if-elseswitch-case 判断状态;
  • 需要后期扩展更多状态。

注意,以上部分内容由AI大模型生成,注意识别!

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

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

相关文章

工业前端组件库重构心法:如何让开发效率提升60%的交互模块设计逻辑

工业前端组件库重构心法:如何让开发效率提升60%的交互模块设计逻辑内容摘要在工业项目开发中,前端组件库是提升开发效率的关键。然而,许多团队的组件库存在设计不合理、维护困难等问题,导致开发效率低下。如果能够重构组件库&…

leetcode 74. 搜索二维矩阵

二分查找经典题目;根据矩阵的特点,不需要把矩阵拉成一维,二维转成一维映射关系为a[i]matrix[⌊i//n⌋][i%n];然后开始二分查找,一直二分的收缩区间;class Solution:def searchMatrix(self, matrix: List[Li…

26考研英语词汇的逻辑笔记(Unit31-43)

行为UNIT 31词汇数量:274 词群数量:16 词群逻辑:行为举止 | 行为标准与原则 给予、收回 | 接受、允许、让步、拒绝 促进、鼓励 | 支持、帮助、资助 破坏相关 | 错误、改正 阻碍、打扰相关 | 禁止、阻止、限制 值得、有利、不利相关 | 有意、故…

Lua(数据库访问)

Lua 数据库访问方法Lua 本身不提供内置的数据库访问功能,但可以通过第三方库实现与多种数据库的交互。以下是常见的 Lua 数据库访问方法:使用 LuaSQL 库LuaSQL 是一个轻量级数据库访问库,支持多种数据库后端(MySQL、PostgreSQL、S…

在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE

在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE 文章目录 在 Dell PowerEdge T440 上通过 iDRAC9 安装 Proxmox VE 1. 前置要求 1.1. 硬件信息(例) 1.2. 准备工作 2. 安装步骤 2.1. 登录 iDRAC9 2.2. 启动虚拟控制台 2.3. 挂载 Proxmox VE ISO 2.4. 设置服务器从虚拟…

window下MySQL安装(三)卸载mysql

window下MySQL安装&#xff08;三&#xff09;卸载mysql 卸载mysql数据库&#xff0c;停止服务&#xff0c;备份文件&#xff0c;删除mysql文件。结束。 停止mysql服务 以管理员身份打开命令提示符或 PowerShell&#xff1a; net stop <服务名称> 示例&#xff1a;net st…

Elasticsearch 深度分页问题与 `search_after` 解决方案

1. 引言 主题&#xff1a;介绍 Elasticsearch 深度分页问题的背景&#xff0c;强调其在处理大规模数据集时的性能瓶颈。核心问题&#xff1a;传统 from/size 分页方式在深层分页&#xff08;例如第500页&#xff09;时&#xff0c;因需要加载和丢弃大量文档&#xff0c;导致内存…

Spring Boot 2整合MyBatis Plus详细指南

1. 环境准备Spring Boot版本&#xff1a;2.x&#xff08;推荐2.7.x&#xff09;MyBatis Plus版本&#xff1a;3.5.x&#xff08;兼容Spring Boot 2&#xff09;数据库&#xff1a;MySQL 8.0&#xff08;其他数据库需调整驱动&#xff09;2. 创建项目并添加依赖在pom.xml中添加核…

Docker镜像导入解析:docker import vs docker load

本文通过Busybox镜像的实战演示&#xff0c;深入剖析两个易混淆命令的技术原理与适用场景一、核心区别速览特性docker importdocker load输入来源容器文件系统快照(docker export输出)完整镜像归档(docker save输出)保留信息仅文件内容完整镜像(层/历史/配置/标签)生成镜像结构…

Android 解决键盘遮挡输入框

本文目录 点击直达Android 解决键盘遮挡输入框代码实现使用注意最后我还有一句话要说梧桐叶上三更雨&#xff0c;叶叶声声是别离。Android 解决键盘遮挡输入框 在安卓中通常可以通过添加android:windowSoftInputMode"adjustResize|stateHidden"的方式来让键盘顶起布…

热门JavaScript库“is“等软件包遭npm供应链攻击植入后门

轻量级 JavaScript 实用工具库 "is" 是 NPM 平台上的热门项目&#xff0c;每周下载量超过 220 万次。然而在 2025 年 7 月 19 日&#xff0c;该库开发者遭遇钓鱼攻击导致账户凭证泄露&#xff0c;攻击者借此发布了包含远程代码执行后门的恶意版本。钓鱼攻击入侵开发者…

如何实现缓存音频功能(App端详解)

本方案提供了符合平台规范的音频缓存实现&#xff0c;通过分层设计确保功能可靠性&#xff0c;同时预留扩展点支持未来优化需求。&#x1f4e6; 音频缓存与播放流程说明 项目目标 实现App端音频资源的自动缓存与播放&#xff1a; 获取远程音频URL下载文件至本地_downloads/目录…

从零到一:Django图书管理系统完整开发实战指南

作者&#xff1a;笙囧同学 身份&#xff1a;中科院计算机大模型方向硕士 | 全栈开发爱好者 座右铭&#xff1a;偷懒是人生进步的阶梯 联系方式&#xff1a;3251736703qq.com 可接课设&#xff0c;论文&#xff0c;专利辅导 全平台账号&#xff1a;笙囧同学 &#x1f4d6; 前言 …

硬件电路设计(基本元器件)

一、电阻 1.电阻的基本概念 电阻是导体对电流阻碍作用的物理量&#xff0c;符号为R&#xff0c;单位为欧姆&#xff08;Ω&#xff09;。电阻的大小由导体的材料、长度、横截面积和温度共同决定。 电阻公式 导体的电阻R可通过以下公式计算&#xff1a; ρ&#xff1a;电阻率&…

R语言常用扩展包

R语言的强大之处很大程度上源于其丰富的扩展包&#xff08;Packages&#xff09;生态系统&#xff0c;目前CRAN&#xff08; Comprehensive R Archive Network&#xff09;上托管的扩展包已超过20,000个。以下是不同领域最常用的核心扩展包分类介绍&#xff1a; 一、数据处理与…

文件相关问题(AI回答)

一个进程打开一个文件&#xff0c;另一个进程能删除这个文件吗DeepSeek-R1 回答完成深度思考完成 用时10秒是的&#xff0c;‌另一个进程可以删除一个已被打开的文件‌&#xff0c;但具体行为取决于操作系统和文件系统。以下是关键细节&#xff1a;1. ‌Linux/Unix 的行为‌‌删…

三相四桥臂SVPWM控制及电机模型

三相四桥臂SVPWM控制策略及其在电机驱动中应用 一、三相四桥臂逆变器拓扑特性 1. 结构优势 中性点控制&#xff1a;第四桥臂独立调节中性点电压&#xff0c;支持不平衡负载电压利用率&#xff1a;相比传统三桥臂提升15.47%&#xff0c;最大线电压达U_{dc}硬件简化&#xff1a;无…

deepseek+飞书多维表格 打造小红书矩阵

通过AI技术平台DeepSeek的数据分析与内容生成能力&#xff0c;结合飞书多维表格的智能化协作管理&#xff0c;实现小红书矩阵账号的高效运营。DeepSeek精准抓取热点趋势并生成爆款文案&#xff0c;飞书多维表格则提供可视化内容排期、多账号数据看板及团队任务分配功能&#xf…

4、如何生成分布式ID?

目录 1、分布式ID介绍 什么是 ID&#xff1f; 什么是分布式 ID&#xff1f; 分布式 ID 需要满足哪些要求? 2、分布式 ID 常见解决方案 1、数据库 示例使用2&#xff1a; 2、数据库号段模式 使用示例2&#xff1a; 一、核心设计思路 二、实现代码 1. 数据库表设计&…

Rust 实战三 | HTTP 服务开发及 Web 框架推荐

往期回顾 Rust 实战二 | 开发简易版命令行工具 grepRust 实战一 | 用 RustRover 开发猜数字游戏Rust 安装与版本更新 代码开源地址&#xff1a;https://github.com/0604hx/rust-journey &#x1f680; Web 框架 名称性能&#xff08;QPS&#xff09;WebSocket / SSEGitHub ⭐…