前端精读:设计模式 State 状态模式 —— 用状态类替换 if-else 分支,实现行为随内部状态切换
文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载State状态模式属于行为型模式其核心价值在于允许一个对象在其内部状态改变时改变它的行为让对象看起来似乎修改了它的类。本文以前端精读周刊《设计模式 - State 状态模式》为主体完整继承其团队接口人、台灯按钮、数据库连接器三个实战案例与 TypeScript 实现并结合周刊仓库内 《robot 源码 - 有限状态机》 的源码级剖析帮助读者在真实业务中判断何时该用状态模式、何时该老老实实用 if-else并掌握多状态类 内部切换的落地写法。一句话理解状态模式状态模式的本质是将一个大 class 一堆 if else 替换为 一堆小 class大 class包含全部状态与全部分支逻辑状态一变判断逻辑成倍膨胀一堆小 class每个小 class 就是一个状态每个状态只负责自己的行为以及如何切换到下一个状态。用一堆状态类代替 if-else 分支会带来更好的可拓展性与可维护性新增一种状态只需新增一个类、改动相关状态的切换目标而不是在一大段 if-else 里小心翼翼地插入新分支。三个案例什么时候会用到状态模式设计模式只有在日常工作里用起来才有价值。以下三个场景分别从职责分发、状态循环、生命周期三个角度帮助你识别状态模式的适用场景。案例一团队接口人职责分发团队由很多同学组成但对外只有一位接口人 TL。这位 TL 可能一会儿和产品经理谈需求一会儿和其他 TL 谈规划一会儿和 HR 谈人事一个人显然忙不过来。TL 的做法是将任务分发给团队中每个同学让他们不直接与产品经理、其他 TL、HR 接触。每位同学只负责一块具体的业务TL 在不同时刻叫上不同的同学让他们出面解决各自专业领域的问题。这样从外部看这位 TL 的团队能力很广从内部看每个人负责的事情非常单一。这与状态模式的思路如出一辙对外是一个统一入口TL / Context对内把不同状态同学拆分各自独立维护。案例二台灯按钮状态循环我们经常见到只有一个按钮但可以调节亮度的台灯其循环是关 - 弱光 - 亮 - 强光 - 关每次按下按钮后要跳转到什么状态和当前状态有关。这可以用 if-else 解决也可以用状态模式解决用 if-else一个Lamp类内部维护currentState枚举click()里写大量分支用状态模式将四个状态封装为四个类每个类自己实现按下按钮后跳转到哪个状态。未来新增一种亮度模式只需要改变部分类而不需要改动所有分支。案例三数据库连接器生命周期数据库连接器在连接前、连接中、连接后其状态显然非常不同。如果仅用一个类描述连接器内部免不了写大量分支语句做状态判断。状态模式给出的方案是创建多个不同状态类——连接前、连接中、连接后。这些状态类继承同一个父类/实现同一个接口在不同时刻Context 内部替换为不同的子类。对外调用方不需要感知内部状态的变化API 稳定对内状态被拆分维护更清晰。意图解释重点是内部状态意图允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。理解这个意图关键在于内部状态三个字状态改变是由对象内部触发的而不是外部强制的因此外部根本无需关心对象是否使用了状态模式。以数据库连接器为例无论这个类是用 if-else 堆砌的还是用状态模式实现的都完全不妨碍它对外提供稳定 API。状态模式的替换只发生在内部所以它实质上是一种内聚的设计模式——把状态相关的复杂逻辑收敛在对象内部对外隐藏细节。结构图State 与 ConcreteState状态模式的经典结构包含两类角色以台灯为例角色含义台灯类比State状态接口台灯的状态抽象ConcreteState具体状态都实现 State台灯的强光、弱光等具体状态State定义状态行为接口例如show()展示当前状态、click()响应按钮按下ConcreteState继承/实现 State 的各个具体状态类每个类内部持有 Context 引用并在触发事件时通过context.setState(...)切换到下一个具体状态Context持有当前状态对象对外暴露统一 API如click()内部将行为委托给当前状态。只要具体状态都符合统一接口这一点成立就满足了状态模式的核心条件。完整代码例子TypeScript 实现台灯状态循环下面的例子使用 TypeScript 编写完整演示了关 - 弱光 - 亮 - 强光 - 关的状态循环abstract class Context { abstract setState(state: State): void; } // 定义状态接口 interface State { // 模拟台灯点亮 show: () string } interface Light { click: () void } // 状态与灯的复合类型 type LightState State Light class TurnOff implements State, Light { context: Context; constructor(context: Context) { this.context context } show() { return 关灯 } // 按下按钮 public click() { this.context.setState(new WeakLight(this.context)) } } class WeakLight implements State, Light { context: Context; constructor(context: Context) { this.context context } show() { return 弱光 } // 按下按钮 public click() { this.context.setState(new StandardLight(this.context)) } } class StandardLight implements State, Light { context: Context; constructor(context: Context) { this.context context } show() { return 亮 } // 按下按钮 public click() { this.context.setState(new StrongLight(this.context)) } } class StrongLight implements State, Light { context: Context; constructor(context: Context) { this.context context } show() { return 强光 } // 按下按钮 public click() { this.context.setState(new TurnOff(this.context)) } } // 台灯 class Lamp extends Context { // 当前状态 #currentState: LightState new TurnOff(this) setState(state: LightState) { this.#currentState state } getState() { return this.#currentState } // 按下按钮 click() { this.getState().click() } } const lamp new Lamp() // 关闭 console.log(lamp.getState().show()) // 关灯 lamp.click() // 弱光 console.log(lamp.getState().show()) // 弱光 lamp.click() // 亮 console.log(lamp.getState().show()) // 亮 lamp.click() // 强光 console.log(lamp.getState().show()) // 强光 lamp.click() // 关闭 console.log(lamp.getState().show()) // 关闭对照输出整个调用链非常清晰Lamp初始状态为TurnOffgetState().show()输出关灯每次lamp.click()都会委托给当前状态的click()由当前状态自己决定切换到下一个状态TurnOff - WeakLight - StandardLight - StrongLight - TurnOff因为Lamp继承Context每个具体状态都持有同一个context引用切换时通过context.setState(...)完成内部状态替换。需要注意代码中的几个关键设计点Context 抽象Lamp继承Context强制约定任何具体状态都可以通过setState替换自己状态持有 Context每个 ConcreteState 构造函数接收context才能在被触发时反向修改 Context 的内部状态复合类型LightState State Light保证Lamp持有的对象同时具备展示状态与响应点击两种能力。其实实现方式有很多种不必拘泥于形式大体上只要保证两点即可由多个类实现不同状态每个类自己实现切换到下一个状态的逻辑。源码佐证有限状态机与状态模式的关系状态模式与有限状态机Finite State Machine是一对紧密相关的概念。周刊仓库中的 《robot 源码 - 有限状态机》 从源码层面剖析了有限状态机的实现恰好可以作为理解状态模式底层机制的补充createMachine(current, states, contextFn)创建状态机保存context内部数据、current当前状态名、states全部状态描述三个属性。若只传入状态对象则取Object.keys(states)[0]作为初始状态。这与台灯例子中Lamp初始化#currentState new TurnOff(this)是对应的——状态机/Context 需要知道自己当前处于哪个状态。state(...args)描述一个状态支持哪些转换并从中过滤出transition普通转换与immediate立即转换。若参数数量为 0则说明这是最终态无法再转换。transition(from, to, ...args)定义从某状态触发某事件后到达的目标状态其中from为null即表示立即转换immediate。转换还可以携带guards守卫执行成功才允许转换与reducers修改状态机内部数据。transitionTo(service, fromEvent, candidates)真正执行状态切换的函数遍历候选转换通过guards(context)校验守卫通过service.context reducers.call(...)更新内部数据最后生成一个current指向新状态的状态机对象。对照可见状态模式与有限状态机的思想同源都强调状态集合 状态切换规则 内部触发。区别在于状态模式更偏面向对象的表达每个状态是一个类行为内聚在类中本文台灯例子即如此有限状态机更偏数据驱动的表达状态与转换被建模为数据states对象 transition描述由统一的引擎负责调度robot 即如此。在实际业务中二者可互为补充状态清晰、转换规则固定的场景用有限状态机管理工具可以先罗列全部状态、避免遗漏且转换安全错误状态下发错误指令不会产生异常状态行为复杂、希望逻辑内聚的场景则可以用经典状态模式。与策略模式的区分状态模式与策略模式结构上非常相似都是接口 多个实现类但意图不同容易混淆。仓库内 《设计模式 - Strategy 策略模式》 明确了两者的差异策略模式定义一系列算法并封装使它们可以相互替换算法可以独立于使用它的客户而变化如地图导航的步行/骑车/开车/公交布局的栅格/流式适配。策略由外部注入可随时替换状态模式状态改变由对象内部触发外部无需感知。每个状态还掌握切换到下一个状态的规则如台灯从弱光到亮到强光。简言之策略是客户选择方案状态是状态决定后续。策略模式的代码例子可参考interface Strategy { doSomething: () void } class Strategy1 implements Strategy { doSomething: () { console.log(实现方案1) } } class Strategy2 implements Strategy { doSomething: () { console.log(实现方案2) } } // 使用外部注入不同策略 new System(new Strategy1()) // 策略1实现的系统 new System(new Strategy2()) // 策略2实现的系统可以看到策略模式的切换发生在构造时外部决定而状态模式的切换发生在内部context.setState由状态类自己调用——这是判断二者最直观的分界线。弊端该用 if-else 时就要用 if-else状态模式并非万能原文明确提醒该用 if-else 的时候还是要用不要但凡遇到 if-else 就使用状态模式那样就是书读傻了。使用状态模式前一定要判断各状态间差异是否很大——只有差异大、行为复杂度高时拆分才有价值使用状态模式后维护性是否真的比 if-else 更好——如果分支简单清晰、状态数量少if-else 反而更直观。同样地策略模式也存在类似警示不要每个分支都套一层策略模式否则策略类数量会失控。当分支逻辑简单、清晰、好维护时直接使用条件分支即可。模式是工具不是教条滥用比不用更糟。总结在合适场景下状态模式可以带来以下收益更符合开闭原则新增状态时只需新增一个状态类、调整相关状态的切换目标无需改动 Context 和其他状态类每个类逻辑更精简、聚焦单个状态类只关心自己是什么、按下按钮后去哪复杂度被均摊到多个小类中对外 API 稳定内部状态如何切换对外完全透明是一种高内聚、低耦合的设计。判断是否采用状态模式的检查清单信号倾向状态数量少、分支简单清晰直接用 if-else / switch状态数量多、各状态行为差异大考虑状态模式未来很可能新增状态状态模式符合开闭原则状态切换规则复杂、需要守卫校验考虑有限状态机见 robot 源码解读延伸阅读本文主体出处设计模式/186.精读《设计模式 - State 状态模式》.md有限状态机源码剖析状态模式的工程化实现源码解读/122.精读《robot 源码 - 有限状态机》.md与状态模式极易混淆的兄弟模式设计模式/187.精读《设计模式 - Strategy 策略模式》.md更多设计模式系列文章见 readme.md 的设计模式目录赞分享文档技术博客教程【免费下载链接】weekly前端精读周刊。帮你理解最前沿、实用的技术。项目地址https://gitcode.com/GitHub_Trending/we/weekly点击查看免费下载相关推荐前端状态模式fe-interview状态转换与行为变化前端状态模式fe interview状态转换与行为变化 引言状态管理的艺术 在前端开发中状态管理是一个永恒的话题。你是否曾经遇到过这样的场景一个按钮根据前端知识库教程Java 设计模式之状态模式State Pattern用状态对象替换巨型条件分支的源码实战指南Java 设计模式之状态模式State Pattern用状态对象替换巨型条件分支的源码实战指南 本文围绕 java design patterns 仓库中示例工程教程DesignPatternsPHP 状态模式State Pattern实战解析用 PHP 8 以对象状态替换巨型条件分支DesignPatternsPHP 状态模式State Pattern实战解析用 PHP 8 以对象状态替换巨型条件分支 本指南围绕 DesignPatt示例工程教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

从狭义到广义,一文讲透知识产权的商业价值与实战代码

从狭义到广义,一文讲透知识产权的商业价值与实战代码

作为一名开发者,我们每天都在与代码打交道。但你有没有想过,你敲下的每一行代码,其实都蕴含着巨大的法律与商业价值? 近期在复习知识产权法时,看到一张关于**“知识产权基本范围”的基础知识点图,让我深受启发。很多程序员和技术管理者往往只关注技术实现,却忽略了代码…

2026/10/4 14:44:09 阅读更多 →
开发日志2026.10.2

开发日志2026.10.2

出去旅游一圈回来了,第一件事就是回来搞游戏。居然直接把GPT登上了,一大步,但是go的额度两句话就用完了,现在使用DeepSeek。去高原湖边捡了石头回来。山脉的躯体,我取一粒 发不了图怎么回事,下次再发

2026/10/4 14:44:09 阅读更多 →
Redis BigKey 到底危险在哪里:内存、阻塞、网络和删除风险怎么排查

Redis BigKey 到底危险在哪里:内存、阻塞、网络和删除风险怎么排查

很多 Redis 问题,表面上看是“偶发卡顿”“接口突然慢”“某个实例内存涨得很快”,最后往下查,常常会落到一个很朴素的原因:某些 key 太大。 这里的“大”不是一个绝对标准。字符串 value 很大,当然是 BigKey&#xff…

2026/10/4 14:44:11 阅读更多 →

最新新闻

GD32F103入门实践:环境配置、工程模板搭建与烧写全流程解析

GD32F103入门实践:环境配置、工程模板搭建与烧写全流程解析

GD32F103入门实践(一)配置环境,搭建工程模板,烧写程序 先说说我为什么写这个系列。这几年国产MCU用得多,GD32F103是绕不开的一颗料。它和STM32F103的引脚、寄存器级兼容,价格又低一截,很多产品…

2026/10/5 14:36:12 阅读更多 →
VMware Workstation Pro 16安装Ubuntu 20.04虚拟机完整指南

VMware Workstation Pro 16安装Ubuntu 20.04虚拟机完整指南

我这些年装 Ubuntu 虚拟机,装到闭着眼睛都能把新建虚拟机的向导点完。每次有新人问“我在 Windows 上想跑 Linux,到底用哪个方案”,我给的建议基本没变过——VMware Workstation Pro 16 搭配 Ubuntu 20.04 LTS。这套组合不算新潮,…

2026/10/5 14:36:12 阅读更多 →
中小企业网络设计课程设计全攻略:VLAN划分到仿真验证一步到位

中小企业网络设计课程设计全攻略:VLAN划分到仿真验证一步到位

简介:这是一份计算机网络课程设计报告文档,主题为中小企业网络设计,面向高校计算机相关专业学生及需要撰写同类课程设计报告的读者。文档以小组形式完成,围绕新盟科技公司等百人以下企业的实际建网场景,从需求分析、可…

2026/10/5 14:36:12 阅读更多 →
C++可视化窗口从零实战:从Win32到消息循环与框架迁移

C++可视化窗口从零实战:从Win32到消息循环与框架迁移

1. 写可视化窗口之前,先想清楚这几件事先直接说结论:用C写可视化窗口,很多人第一反应是“去网上找个图形库”,然后一头扎进Qt、wxWidgets、SFML的海洋里,折腾半天连窗口都没弹出来,反而被编译错误劝退。我见…

2026/10/5 14:36:12 阅读更多 →
如何挑选品质可靠的水泥雕塑品牌?专家揭秘选择技巧

如何挑选品质可靠的水泥雕塑品牌?专家揭秘选择技巧

如何挑选品质可靠的水泥雕塑品牌?专家揭秘选择技巧在城市公共艺术和景观设计中,水泥雕塑扮演着至关重要的角色。它们不仅能够提升环境的艺术氛围,还能成为城市的标志性符号。然而,市场上水泥雕塑品牌众多,如何挑选一个…

2026/10/5 14:36:12 阅读更多 →
YOLOv5源码深度解析:用Debug逐行追踪特征图变化

YOLOv5源码深度解析:用Debug逐行追踪特征图变化

写代码的人应该都有过这种经历:模型能跑通,精度也还算行,但你要是突然问一句“这中间特征图到底是怎么变的?”,自己心里立刻就没底了。我接触yolov5源码这件事拖了很久,原因很简单——平时调用detect.py和t…

2026/10/5 14:35:12 阅读更多 →

日新闻

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

马斯克杀回智能体战场,Grok 4.5万亿参数撑腰,Cursor接手数字白领项目:用TaoToken统一Key跑通多模型Agent工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:22 阅读更多 →
AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

AI编程工具插件机制详解:plugin.json配置与加载失败排查指南

1. 从“plugins”这个词说起:它到底在解决什么问题如果你最近在折腾 AI 编程工具,尤其是 Cursor、Codex CLI、Claude Code 这类带 CLI 的编辑器或命令行助手,那你大概率绕不开一个词——plugins。这个词本身不新鲜,从浏览器到 IDE…

2026/10/5 0:00:23 阅读更多 →
第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

第26课:OpenClaw|日志审计与问题诊断:把日志链路改到 TaoToken 的排查清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 0:00:23 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 5:06:42 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 1:10:22 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/5 3:06:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 11:40:45 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 9:43:54 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 20:14:29 阅读更多 →