设计模式 18 · 状态模式

发布时间:2026/8/11 0:37:42

设计模式 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,变成一组清晰的状态类 明确的流转关系(状态机),新增状态不动或少动已有代码,还天然杜绝非法流转。它和策略是结构几乎相同的双胞胎,但策略是平行地选一个算法、由外部指定、通常不变;状态是按规则流转的一串阶段、自动切换、不断变化——能画出状态转移图的就是状态。订单生命周期是它最经典的应用。下一篇我们讲命令模式——它把一个请求/操作本身封装成对象,从而能把操作排队、记录、撤销和重做,典型如订单操作的撤销功能。

相关新闻

04-手眼标定理论:眼在手上、眼在手外适用场景

04-手眼标定理论:眼在手上、眼在手外适用场景

2026/8/11 0:27:41

手眼标定理论:眼在手上、眼在手外适用场景 作者:黒漂技术佬 上一篇说外参矩阵是手眼标定算出来的。这篇就来讲手眼标定到底在标什么、怎么标、两种安装方式有什么区别。理论篇,公式较多,但我会用人话翻译每一个符号。 一、手眼标定的目的:求什么 回顾上一篇,像素坐标转机…

03-像素坐标转机械臂世界坐标原理详解

03-像素坐标转机械臂世界坐标原理详解

2026/8/11 0:27:41

像素坐标转机械臂世界坐标原理详解 作者:黒漂技术佬 YOLO 说目标在 (320, 240) 像素位置,深度相机说距离 0.5 米。但机械臂只认自己坐标系下的毫米数。这中间怎么转?这篇文章把数学原理掰开揉碎讲清楚。 一、四个坐标系:先搞清楚谁是谁 在视觉抓取中,一个点的坐标会在四个…

别人用AI没事,你用就被抓?专科论文避坑5条铁律

别人用AI没事,你用就被抓?专科论文避坑5条铁律

2026/8/11 0:27:41

别人用AI没事,你用就被抓?专科论文避坑5条铁律室友用AI一天写完了专科毕业论文初稿,查重一次过。你也用AI写,交上去被退回——AI率37%,超了红线。 你懵了。用的都是同一个工具,差别在哪? 2026年…

AI生成内容高效转Word:Markdown与Pandoc工作流实践

AI生成内容高效转Word:Markdown与Pandoc工作流实践

2026/8/11 1:38:02

1. 项目概述:从AI长文到格式完好的Word文档如果你经常使用DeepSeek或豆包这类AI助手来生成技术文档、项目报告或者学习笔记,肯定遇到过这样的烦恼:AI生成的内容逻辑清晰、结构完整,但当你兴冲冲地复制粘贴到Word里准备进一步编辑时…

CAD动态块图库:参数化设计提升绘图效率的必备技能

CAD动态块图库:参数化设计提升绘图效率的必备技能

2026/8/11 1:38:02

1. 这篇文章真正要解决的问题如果你是一名建筑、机械或室内设计领域的CAD绘图员,或者是一名需要频繁使用AutoCAD的学生、工程师,那么你一定经历过这样的场景:为了画一个简单的门、窗、家具或者一个标准螺栓,你需要反复绘制、复制、…

Claude Code 命令大全:从自然语言到高效编程的实战指南

Claude Code 命令大全:从自然语言到高效编程的实战指南

2026/8/11 1:38:02

1. 项目概述:为什么你需要这份“夯爆了”的命令大全?如果你是一名开发者,或者正在学习编程,那么最近一定被“Claude Code”这个词刷屏了。它不是什么新的编程语言,也不是某个IDE,而是由Anthropic公司推出的…

掌握Claude Code斜杠命令:提升开发效率的实战指南

掌握Claude Code斜杠命令:提升开发效率的实战指南

2026/8/11 1:38:02

1. 项目概述:为什么你需要掌握Claude Code的斜杠命令? 如果你是一名开发者,或者日常工作中需要频繁与代码打交道,那么你很可能已经听说过或者正在使用Claude。但你是否真正挖掘过它那个名为“Claude Code”的特定模式,…

海淀区创业扶持机构推荐:【博亚信诚】助企赋能

海淀区创业扶持机构推荐:【博亚信诚】助企赋能

2026/8/11 1:38:02

开篇语:海淀区作为北京国际科技创新中心的核心承载地,集聚大量高校科研资源、科创企业与创业人才,区域内创业扶持服务机构数量持续增长,不同机构的服务能力、资源储备、落地成效存在明显差异,很多初创企业在遴选合作机…

springboot二手家电管理平台

springboot二手家电管理平台

2026/8/11 1:28:01

课题背景 随着互联网技术的快速发展和电子商务的普及,二手商品交易市场逐渐成为人们日常生活中不可或缺的一部分。二手家电作为其中的一个重要类别,因其价格低廉、实用性高而受到广泛关注。然而,传统的二手家电交易模式存在信息不对称、交易效…

比较好的亚太EMBA,问了6位校友师资差别真的挺大

比较好的亚太EMBA,问了6位校友师资差别真的挺大

2026/8/10 5:58:32

比较好的亚太EMBA核心差异先看什么?对于希望兼顾工作与系统管理能力提升的亚太区高管而言,筛选匹配度高的EMBA项目时,师资配置是决定学习体验与实际收获的核心要素之一。我们结合3-4个公开信息透明、办学历史较长的亚太区主流EMBA项目特点&am…

备考3个月对比6份资料 海外游学的亚洲EMBA面试注意点

备考3个月对比6份资料 海外游学的亚洲EMBA面试注意点

2026/8/10 7:54:12

备考海外游学的亚洲EMBA面试,核心要围绕项目国际化设计逻辑、个人跨文化管理经验匹配度两个维度准备,避免把游学模块等同于普通旅游参访的认知偏差。不少备考者花3个月对比6份资料,却容易忽略面试官对“国际视野落地能力”的考察——比如香港…

比较好的国内EMBA,问了二十位校友聊透人脉价值

比较好的国内EMBA,问了二十位校友聊透人脉价值

2026/8/10 7:19:21

比较好的国内EMBA核心差异体现在哪些方面?比较好的国内EMBA的核心长期价值,很大程度上依托于校友网络的连接质量与资源生态的活跃度,这也是不少高管在择校时优先考量的因素。我们结合3-4个市场关注度较高的项目公开信息,从课程、师…

Unity新手入门:从零搭建开发环境与核心概念解析

Unity新手入门:从零搭建开发环境与核心概念解析

2026/8/11 0:07:41

1. 项目概述:为什么Unity是游戏开发者的首选起点如果你对游戏开发感兴趣,或者想进入这个充满创造力的行业,那么“Unity”这个名字你肯定不陌生。它几乎是所有新手开发者、独立游戏团队,甚至是一些3A大厂在特定项目上的首选引擎。为…

Agency-Agents 智能体系统从零搭建实战指南

Agency-Agents 智能体系统从零搭建实战指南

2026/8/11 0:07:41

在开发复杂应用时,我们常常遇到单一模型难以兼顾全局规划与细节执行的困境。有时候,模型擅长创意生成却在逻辑推理上稍显吃力,或者精于代码编写却缺乏对业务上下文的深刻理解。为了解决这个问题,多智能体协作架构应运而生&#xf…

MiniMax 权益码 Token Plan 套餐 9 折优惠,Token Plan 共建邀请计划 至2026.8.31

MiniMax 权益码 Token Plan 套餐 9 折优惠,Token Plan 共建邀请计划 至2026.8.31

2026/8/11 0:07:41

🚀 MiniMax Token Plan MiniMax 推出全新 Token 计划,新增语音、音乐、视频和图片生成权益。 用户邀请好友可享双重福利 订阅一份套餐,解锁最新模型 —— 前沿 Coding 能力、1M 超长上下文、原生多模态,图文音视频共用套餐额度。 …

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/8 5:07:31

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/9 13:42:46

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/8 2:30:15

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…