资讯动态

前端精读:设计模式 State 状态模式 —— 用状态类替换 if-else 分支,实现行为随内部状态切换

发布时间:2026/10/3 7:28:02 来源:尧图企业网站定制
文档技术博客教程【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑