设计模式 18 · 状态模式
前面我们埋过好几次钩子:策略那篇说策略是平行选一个,状态是链式流转,这一篇终于轮到状态模式(State)正式登场,把这条界限彻底讲透。它和策略模式的代码结构几乎一模一样(都是持有一个接口引用、委托调用),却是行为型里最经典的一对双胞胎——长得像,内核完全不同。状态模式解决的问题一句话:一个对象的行为,随它自身所处的状态而变化,而且状态之间会按规则相互流转。关键词是行为随状态变和状态会流转。生活里的例子随处可见:一部电梯,在运行中你按开门键没反应,在停靠中按了才开门——同一个动作(按开门),在不同状态下行为完全不同;而电梯的状态还会流转:停靠→运行→停靠。红绿灯也是:红灯亮着,时间到了自动变绿,绿变黄,黄变红,状态按固定规则一环扣一环地转。我们的订单场景里,有一个教科书级的状态机例子:订单的生命周期。一笔订单会经历待付款 → 已付款 → 已发货 → 已完成这一串状态,中间还可能取消。而订单的行为强烈依赖于它当前的状态:待付款的订单可以支付、可以取消;已发货的订单不能取消、只能确认收货;“已完成的订单什么操作都不能做。同样是取消这个动作,在不同状态下,有的允许、有的直接拒绝。如果用一堆if-else判断当前状态来决定行为,代码会变成一个又臭又长、状态一多就失控的泥潭。状态模式,就是把每个状态做成一个独立的类,让状态自己决定在我这个状态下,各个动作该怎么做、该流转到哪个状态”。这篇文章按这条线索展开:先看用状态字段 一堆 if-else 判断的困境;再引出状态模式如何把每个状态对象化;然后讲清它的角色、以及状态流转的两种驱动方式;接着重点辨析状态与策略这对双胞胎(这是本篇的核心);最后给出适用边界。贯穿例是订单状态机。目录用 if-else 判断状态的困境状态模式:把每个状态对象化角色,与状态流转谁来驱动状态 vs 策略:一对双胞胎的彻底辨析什么时候用状态模式一、用 if-else 判断状态的困境看订单的操作。订单有个当前状态字段,每个操作都要先判断当前状态、再决定怎么做:publicclassOrder{privateStringstatus;// 待付款、已付款、已发货、已完成、已取消// 支付操作publicvoidpay(){if(status.equals(待付款)){status已付款;// 只有待付款能支付}else{thrownewRuntimeException(当前状态不能支付);}}// 取消操作publicvoidcancel(){if(status.equals(待付款)){status已取消;// 待付款能取消}elseif(status.equals(已付款)){status已取消;// 已付款也能取消(要退款)}else{thrownewRuntimeException(当前状态不能取消);// 已发货/已完成不能取消}}// 发货操作publicvoidship(){if(status.equals(已付款)){status已发货;}else{thrownewRuntimeException(当前状态不能发货);}}// ... 每个操作都是一坨状态判断}这段代码能跑,但随着状态和操作变多,它会迅速失控:每个方法都是一坨 if-else:每个操作(pay/cancel/ship/confirm…)内部,都要把所有相关状态判断一遍。状态一多,每个方法都又臭又长。状态转移规则散落各处:待付款能干什么、能转到哪些状态这个规则,被打散在 pay、cancel、ship 各个方法里。你想搞清楚待付款状态的完整行为,得把所有方法翻一遍。违反开闭原则:新增一个状态(比如退款中),你得回到每一个操作方法里,都加一个else if分支。改一处,全都要动、全都要重测。容易出非法流转:全靠人肉判断,一不小心就可能让订单从已完成又跳回待付款这种非法状态,而编译器毫不知情。问题的根源是:状态只是一个字符串字段,而每个状态下的行为和转移规则被硬编码、打散在各个操作方法的 if-else 里。我们真正想要的是:把每一个状态都变成一个独立的对象,让这个状态对象自己封装在我这个状态下,各个操作该怎么响应、该流转到哪个状态。这样,待付款的所有规则都集中在待付款状态这一个类里,清清爽爽。这就是状态模式。二、状态模式:把每个状态对象化状态模式的做法:定义一个状态接口,声明所有操作;每个具体状态是一个类,实现在本状态下这些操作各自怎么做、流转到哪个状态;订单(上下文)持有一个当前状态对象,把操作委托给它,并允许状态切换。第一步,定义状态接口(声明所有操作):publicinterfaceOrderState{voidpay(OrderContextctx);voidcancel(OrderContextctx);voidship(OrderContextctx);// ... 所有操作}第二步,每个状态是一个类,封装本状态下的行为和转移:// 待付款状态publicclassPendingPayStateimplementsOrderState{publicvoidpay(OrderContextctx){System.out.println(支付成功);ctx.setState(newPaidState());// 流转到已付款}publicvoidcancel(OrderContextctx){System.out.println(订单已取消);ctx.setState(newCancelledState());// 流转到已取消}publicvoidship(OrderContextctx){thrownewRuntimeException(未付款不能发货);// 本状态不允许}}// 已付款状态publicclassPaidStateimplementsOrderState{publicvoidpay(OrderContextctx){thrownewRuntimeException(请勿重复支付);}publicvoidcancel(OrderContextctx){System.out.println(已付款订单取消,发起退款);ctx.setState(newCancelledState());}publicvoidship(OrderContextctx){System.out.println(已发货);ctx.setState(newShippedState());// 流转到已发货}}// ShippedState、CompletedState、CancelledState 同理...第三步,上下文(订单)持有当前状态,把操作委托出去:publicclassOrderContext{privateOrderStatestatenewPendingPayState();// 初始状态:待付款publicvoidsetState(OrderStatestate){this.statestate;}// 所有操作,都委托给当前状态对象去处理publicvoidpay(){state.pay(this);}publicvoidcancel(){state.cancel(this);}publicvoidship(){state.ship(this);}}用起来,订单的行为随状态自动变化,流转由状态自己驱动:OrderContextordernewOrderContext();// 待付款order.pay();// 支付成功 → 自动流转到已付款order.ship();// 已发货 → 自动流转到已发货order.cancel();// 抛异常:已发货不能取消对比第一节,升级点非常清晰:每个状态的所有规则(本状态下各操作怎么响应、转到哪)都集中在它自己的类里,一个if-else都没有;OrderContext只管把操作委托给当前状态;要加一个退款中状态,新增一个RefundingState类就行,已有状态类基本不用动。订单当前能干什么这件事,由当前是哪个状态对象来决定,而不是靠满地的状态判断。用一张图看这个状态机最清楚:图里最该记住的,是那张状态之间带箭头的流转图——它就是业务上的订单状态机。状态模式的精髓,就是把这张状态图,从散落的 if-else 里,变成一组各自独立的状态类 它们之间明确的流转关系。哪些流转是合法的,一目了然地写在各状态类里;非法的流转(比如已完成跳回待付款)压根没有对应代码,自然就杜绝了。三、角色,与状态流转谁来驱动状态模式的角色,三个:角色本例中是谁职责上下文(Context)OrderContext持有当前状态,把操作委托给它抽象状态(State)OrderState接口声明各状态共有的操作具体状态(ConcreteState)PendingPayState等封装本状态的行为 流转逻辑一个关键的设计问题:状态的流转(从一个状态切到下一个),该由谁来驱动?有两种做法:由状态对象自己驱动(我们上面用的):每个状态在处理操作时,自己决定处理完该转到哪个状态,调用ctx.setState(...)完成切换。好处是流转逻辑高度内聚——待付款之后去哪这个规则,就写在PendingPayState里。缺点是状态类之间产生了依赖(PendingPayState得知道PaidState的存在)。由上下文统一驱动:状态对象只处理行为、返回结果,由上下文根据结果决定切到哪个状态。好处是状态之间解耦,流转规则集中在一处。缺点是上下文又变重了一点。实际项目里两种都有。状态自己驱动更符合状态模式的原味(把状态机分散进各状态),适合流转规则相对固定的场景;上下文驱动适合流转规则复杂、需要集中管理的场景。对于订单这种流转清晰的状态机,状态自驱通常更自然。四、状态 vs 策略:一对双胞胎的彻底辨析这是本篇的重头戏。状态和策略,是设计模式里最像的一对——把它们的类图画出来,几乎完全一样:都有一个 Context 持有一个接口引用,都把行为委托给具体实现,都用多态替代了条件分支。很多人到这里就懵了:它们到底有什么区别?区别不在结构,而在意图和用法,有三个关键差异:差异一:各个实现之间有没有关系。策略的各个算法是完全平行、互相独立的。运费的包邮策略和按重策略之间,毫无关系,谁也不知道谁的存在。状态的各个状态是互相关联、会流转的。“待付款知道自己之后会变成已付款”,“已付款知道自己会变成已发货”——状态之间有明确的转移关系,构成一张状态图。差异二:谁来决定用哪个实现。策略:由外部(客户端)决定用哪个策略,主动setStrategy(new WeightShipping())塞进去。策略自己不会切换自己。状态:通常由状态自己(或上下文)根据业务规则自动流转到下一个状态。你不会从外面设置订单是什么状态,而是订单在操作过程中自己转过去。差异三:切换的频率和意图。策略:一旦选定,通常在整个过程中不变(这一单就用按重计费)。切换是换一种做法。状态:在对象生命周期里不断流转(订单从待付款一路变到完成)。切换是进入了下一个阶段。用一句话彻底记住:策略是横向地、平行地选一个算法(选择),状态是纵向地、按规则流转的一串阶段(流转)。或者更形象:策略像是点菜——菜单上的菜互不相干,你挑一个;状态像是闯关——一关通了自动进下一关,关卡之间有顺序。结构骗人,意图不骗人。还有一个实用的判断技巧:看有没有状态转移图。如果你能为这些实现画出一张从 A 状态到 B 状态的箭头图,那就是状态模式;如果这些实现之间画不出任何箭头、纯粹是平级的选项,那就是策略模式。订单能画出状态机图 → 状态;运费的几种算法画不出流转 → 策略。五、什么时候用状态模式适合用状态模式的信号:一个对象的行为明显依赖于它的状态,不同状态下同一操作的行为差异很大;存在明确的状态流转规则(能画出状态机图);代码里出现了大量根据状态字段做 if-else/switch的判断,且状态和操作都会增加。不必用的信号:状态就两三个、行为差异也不大——那简单的 if-else 或枚举更直白,套状态模式凭空多出一堆状态类,是过度设计;各状态之间没有流转关系,只是平级选项——那是策略,不是状态;状态机极其复杂(几十个状态、上百条流转规则)——那可能需要专门的状态机引擎/工作流框架(如 Spring StateMachine),手写状态类会难以维护。判断的核心还是那句话:先确认真的存在行为随状态变 状态按规则流转这个结构(能画出状态机图),状态模式才值得上。而且要和策略分清楚——别把一堆平行算法硬说成状态。小结。状态模式把一个对象的每个状态都变成一个独立的状态类,让状态自己封装在本状态下各操作怎么响应、该流转到哪个状态,上下文只持有当前状态对象并委托操作——从而把散落在各方法里的状态 if-else,变成一组清晰的状态类 明确的流转关系(状态机),新增状态不动或少动已有代码,还天然杜绝非法流转。它和策略是结构几乎相同的双胞胎,但策略是平行地选一个算法、由外部指定、通常不变;状态是按规则流转的一串阶段、自动切换、不断变化——能画出状态转移图的就是状态。订单生命周期是它最经典的应用。下一篇我们讲命令模式——它把一个请求/操作本身封装成对象,从而能把操作排队、记录、撤销和重做,典型如订单操作的撤销功能。

相关新闻

树状数组与线段树的区别、联系及应用场景7

树状数组与线段树的区别、联系及应用场景7

树状数组与线段树的区别数据结构特性树状数组(Fenwick Tree):基于二进制索引的紧凑结构,仅支持前缀和查询与单点更新。线段树(Segment Tree):基于区间划分的二叉树结构,支持区间查询…

2026/8/11 0:36:18 阅读更多 →
跳表结构在高并发系统中的应用与优势分析7

跳表结构在高并发系统中的应用与优势分析7

跳表结构的基本原理跳表的定义与核心思想跳表与平衡树、哈希表的对比跳表的层级结构与查找、插入、删除操作的时间复杂度分析高并发系统的核心挑战高并发场景下的性能瓶颈(如锁竞争、缓存一致性)传统数据结构(如B树、红黑树)在高并…

2026/8/11 0:36:18 阅读更多 →
基于空间局部性的排序算法性能重构思路7

基于空间局部性的排序算法性能重构思路7

引言空间局部性在计算机科学中的重要性排序算法性能与缓存利用的关系研究背景与动机:现有排序算法在缓存效率上的局限性空间局部性基础理论空间局部性的定义与原理缓存层次结构(L1/L2/L3)与性能影响数据访问模式对缓存命中的影响传统排序算法…

2026/8/11 0:36:18 阅读更多 →

最新新闻

类似WorkBuddy的企业办公Agent怎么选?TRAE Work与多款主流工具深度对比

类似WorkBuddy的企业办公Agent怎么选?TRAE Work与多款主流工具深度对比

随着人工智能技术的飞速发展,企业对AI工具的需求已不再局限于简单的单句对话,而是转向能够理解复杂业务流程、支持多任务并行、提供端到端解决方案的“企业级办公Agent”。在这条赛道上,以专家协同为特色的 WorkBuddy 吸引了大量关注。那么&a…

2026/8/11 1:24:40 阅读更多 →
2026国内隧道代理服务商推荐:携趣、快代理、全民代理怎么选?

2026国内隧道代理服务商推荐:携趣、快代理、全民代理怎么选?

隧道代理是近年来企业数据业务中使用较多的一类代理产品。与传统短效代理不同,隧道代理不需要用户反复提取新的IP地址。程序只需配置固定入口,后端IP调度由服务商云端完成。对于不想自行维护代理池的企业,隧道代理能够明显降低开发和运维成本…

2026/8/11 1:24:40 阅读更多 →
【Bug已解决】[Anima] Add img2img capability 解决方案

【Bug已解决】[Anima] Add img2img capability 解决方案

【Bug已解决】[Anima] Add img2img capability 解决方案 一、现象长什么样 Anima 是一个动漫风格的图像生成模型(diffusers 接入)。它的 pipeline 最初只支持文生图(txt2img),用户想做「图生图」(拿一张草…

2026/8/11 1:24:40 阅读更多 →
iQOO Z10 Turbo国补版深度评测:天玑8400满血性能与7620mAh蓝海电池如何定义学生电竞旗舰

iQOO Z10 Turbo国补版深度评测:天玑8400满血性能与7620mAh蓝海电池如何定义学生电竞旗舰

1. 这篇文章真正要解决的问题对于预算有限但又渴望畅快游戏体验的学生党来说,选手机是个永恒的难题。市面上“性价比”机型众多,但往往在性能释放、续航和游戏体验上顾此失彼。你可能会遇到:手机刚玩半小时《原神》就烫手降频,团战…

2026/8/11 1:24:40 阅读更多 →
20W高功率射频整流器设计:基于ADS的2.45GHz无线能量传输实战

20W高功率射频整流器设计:基于ADS的2.45GHz无线能量传输实战

这次我们来看一个面向无线能量传输和能量收集应用的高功率射频整流器设计项目。这个项目的核心目标是在2.45GHz的ISM频段,实现高达20W的直流输出功率,并保持较高的整流效率。对于从事RFID、无线传感器网络、无人机无线充电或微波输能研究的工程师和学生来…

2026/8/11 1:24:39 阅读更多 →
TRAE Work 与 WorkBuddy 选型决策:基于工作流形态与任务组织的深度对比指南

TRAE Work 与 WorkBuddy 选型决策:基于工作流形态与任务组织的深度对比指南

在当前的办公 AI 领域,能够协助用户处理文档、演示文稿、深度调研及复杂工作流的 AI 助手正经历快速演进。其中,字节跳动推出的 TRAE Work 与定位为多智能体协同办公平台的 WorkBuddy 成为众多个人、团队与自由职业者关注的焦点。面对这两款同样覆盖文档…

2026/8/11 1:23:39 阅读更多 →

日新闻

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南

如何用Video2X实现专业级视频画质提升:AI视频增强完整指南 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/v…

2026/8/11 0:00:02 阅读更多 →
前后端分离项目中控制台与接口工具数据差异排查指南

前后端分离项目中控制台与接口工具数据差异排查指南

1. 问题现象解析:控制台与Apifox的数据差异 最近在调试一个前后端分离项目时,遇到了一个典型问题:后端服务在本地开发环境控制台能正常输出查询数据,但通过Apifox测试时却返回空结果。这种"控制台有数据,接口工具…

2026/8/11 0:00:03 阅读更多 →
AI编程实战:从Claude Code踩坑到游戏开发入门

AI编程实战:从Claude Code踩坑到游戏开发入门

1. 从“AI能帮我做游戏”到“AI让我重新学编程”最近身边不少朋友,尤其是一些非技术背景、但对游戏开发有浓厚兴趣的朋友,都在问我同一个问题:“听说现在用Claude Code这种AI编程工具,小白也能做游戏了,是真的吗&#…

2026/8/11 0:00:03 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/11 1:08:05 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/11 1:08:05 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/11 1:08:05 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/10 17:07:33 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/11 1:08:06 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/10 17:07:33 阅读更多 →