行为型模式:对象之间的默契配合

建型管"对象怎么来",结构型管"对象怎么搭",行为型管"对象怎么动"。

写业务时最头疼的就是:

  • 一个操作要通知一堆人——观察者
  • 流程有多个步骤,但中间步骤可能变化——模板方法
  • 同一件事有多种做法,要能切换——策略
  • 操作要支持撤销——命令
  • 对象状态变了,行为也跟着变——状态

行为型模式一共 11 种,这里挑最常用的 8 种详讲,另外 3 种简述。

一、策略模式(Strategy)

一句话

定义一系列算法,把它们各自封装起来,让它们可以互相替换。

策略模式的核心不是"封装"本身,而是把"用哪个算法"的决定权从执行者手里拿走,交给调用方。执行者只管执行,选择谁来执行由外部决定。

生活中的例子

  • 导航软件选路线:同样从 A 到 B,可以选"最短距离"、"最少时间"、"避开高速"、"避开收费"——每种是一个策略,随时切换。导航引擎本身不变,换的只是路线计算算法。
  • 商场打折:满减、打折、买一送一、会员价——不同促销活动就是不同策略,换季了就切换。收银系统不需要改,只需要插入新的促销策略。
  • 支付方式:支付宝、微信、银行卡、货到付款——结账时选一种,背后执行逻辑完全不同。
  • 排序算法:小数组用插入排序,大数组用快排,基本有序用 TimSort——根据数据特征切换算法。

为什么需要它

电商系统算运费,一开始只有两种规则,直接 if-else 写完了:

java

// 第一版,看起来还好 if (type.equals("free")) { return 0; } else if (type.equals("weight")) { return weight * 5; }

半年后,业务加了 VIP 折扣、节日特惠、跨城配送、保价服务……代码变成这样:

java

// 不用策略模式的写法——if-else 地狱 if (type.equals("free")) { return 0; } else if (type.equals("weight")) { return weight * 5; } else if (type.equals("distance")) { return distance * 2; } else if (type.equals("vip")) { return weight * 5 * 0.8; } else if (type.equals("festival")) { return weight * 3; } else if (type.equals("insurance")) { return weight * 5 + price * 0.01; } // 每加一种就多一个 if,整个方法越来越长 // 改 VIP 逻辑要在这里找,改节日逻辑也要在这里找 // 测试一个策略,却要加载整个方法

这段代码有三个问题:

  1. 新增策略要改这个方法——违反了开闭原则,应该对扩展开放、对修改关闭
  2. 所有策略挤在一个类里——互相干扰,一个策略改错了影响所有人
  3. 无法复用——另一个模块也要算运费,只能复制粘贴这一坨 if-else

策略模式的解法:把每个 if 分支拆成一个独立的类

什么时候用,什么时候别用

适合用策略模式:

  • if-else / switch 里每个分支逻辑超过几行,且预期还会继续增加
  • 同一件事有多种实现方式,且需要在运行时切换(比如 A/B 测试不同推荐算法)
  • 多个类只有行为不同,可以提取成策略让主类瘦下来

不适合用策略模式:

  • 只有两三种固定策略,且以后也不会增加——直接写死或简单 if-else 更清晰
  • 策略之间需要共享大量内部状态——说明它们本质上不是独立算法,可能需要模板方法
  • 调用方需要知道策略的具体类型才能工作——这破坏了策略模式的封装前提

示意图

代码

java代码

上面VipShipping组合了基础策略,这是策略模式的一个常见用法——策略可以嵌套组合,避免写VipWeightShippingVipDistanceShipping这类笛卡尔积式的爆炸子类。

python代码

框架中的实际应用

  • Spring SecurityAuthenticationStrategy:同一个认证流程,可以切换"第一个成功即可"、"全部必须成功"、"必须一个也不成功"等策略。
  • JavaComparatorCollections.sort(list, comparator)中的comparator就是一个策略,决定怎么排序。list.sort((a, b) -> a.name.compareTo(b.name))这个 lambda 也是策略。
  • Hibernate的缓存策略、锁策略:读写缓存、只读缓存、非严格读写——同一套 ORM 逻辑,插入不同的缓存策略。
  • Webpack / Vite的压缩策略:构建时根据环境切换terseresbuild、不压缩——典型的策略模式。

二、观察者模式(Observer)

一句话

定义一对多的依赖关系,一个对象状态变了,所有依赖它的对象自动收到通知。

生活中的例子

  • 微信公众号:你关注了一个号,博主发文章,所有粉丝同时收到推送。你不用每天打开看有没有更新——有更新它主动告诉你。
  • 股票价格提醒:设好"茅台跌破 1500 提醒我"。股价一变,系统自动通知所有设了提醒的人。
  • 红绿灯:灯变绿,所有等着的车同时收到"可以走了"的信号。灯不需要一辆辆通知。
  • 群聊消息:一个人在群里发消息,群里所有人都收到。发送者不需要知道群里有谁。

结构图

代码

java代码

python代码

Spring 的ApplicationEvent、前端的addEventListener、Vue 的$emit、消息队列的 Pub/Sub,本质全是观察者模式。

三、模板方法模式(Template Method)

一句话

在父类中定义算法的骨架,把某些步骤延迟到子类实现。

生活中的例子

  • 泡茶 vs 泡咖啡:步骤都是"烧水 → 冲泡 → 倒杯子 → 加调料",但"冲泡"和"加调料"不同:茶是泡茶叶+加柠檬,咖啡是冲咖啡粉+加糖奶。
  • 考试:所有学生的考试流程一样(发卷 → 答题 → 收卷 → 评分),但每个人答的内容不同。
  • 做菜:热锅 → 放油 → 放食材 → 调味 → 出锅。每道菜的"食材"和"调味"不同,但流程骨架一样。

结构图


三个角色:

  • AbstractClass(抽象父类):定义流程骨架,固定步骤自己实现,变化步骤声明为抽象
  • ConcreteClass(具体子类):只实现变化的步骤,流程顺序由父类控制
  • 模板方法final修饰,防止子类修改流程顺序

代码

java代码

python代码

流程只定义一次,变化的部分交给子类。好莱坞原则:"别打电话给我们,我们会打给你。"

四、命令模式(Command)

一句话

把一个请求封装成一个对象,从而支持撤销、排队、日志等操作。

生活中的例子

  • 餐厅点菜:你对服务员说"一份红烧肉"。服务员写到单子上(命令对象),拿到厨房。厨师按单做菜。如果你说"那道菜取消",服务员撕掉那张单(撤销)。
  • 遥控器按钮:遥控器上每个按钮都是一个命令——开灯、关灯、调亮度。按钮不知道灯怎么工作,它只知道"触发这个命令"。
  • Git commit:每次 commit 是一个命令对象,记录了做了什么改动。git revert就是执行这个命令的 undo。
  • Excel 的 Ctrl+Z:每个操作被记成命令对象,按 Ctrl+Z 就是取出最近一个命令执行它的 undo。

结构图


四个角色:

  • Command(接口):统一execute()+undo(),Invoker 只认识它
  • ConcreteCommand(具体命令):封装一次操作 + 它的撤销逻辑,持有 Receiver 引用
  • Invoker(调用者):管理命令历史栈,不关心命令干了什么
  • Receiver(接收者):真正执行操作的对象,被命令持有

代码

java代码

python代码

五、状态模式(State)

一句话

允许对象在内部状态改变时改变它的行为,看起来像换了一个类。

生活中的例子

  • 红绿灯:红灯状态只能停,绿灯状态只能走,黄灯状态要减速。灯的行为完全取决于当前状态,到了时间自动切换。
  • 手机:锁屏状态按电源键解锁,解锁状态按电源键锁屏,关机状态按电源键开机——同一个按钮,不同状态做不同的事。
  • 电梯:开门状态不能运行,运行状态不能开门,故障状态什么都不能做——每个状态有自己的规则。
  • 网购订单:待支付→已支付→已发货→已收货。每个状态下能做的操作不同(待支付可以取消,已发货不能取消只能拒收)。

结构图

三个角色:

  • State(状态接口):定义所有状态下可触发的行为,Context 只认识它
  • ConcreteState(具体状态):实现该状态允许的操作,不允许的操作直接报错,并负责切换到下一个状态
  • Context(上下文):持有当前状态,把行为委托给它,自己不写 if-else

代码

java代码

python代码

六、责任链模式(Chain of Responsibility)

一句话

把请求沿着一条链传递,每个节点自己决定处理还是传给下一个。

生活中的例子

  • 公司请假审批:请 1 天组长批,请 3 天经理批,请 7 天总监批,超过 7 天找 CEO。你只需要提交申请,系统自动沿链条往上传。
  • 客服系统:用户问题先到机器人,机器人答不了转人工,人工客服搞不定转主管,主管搞不定转经理。
  • Java 异常处理:异常从内层 catch 往外层抛,直到有人处理。
  • 中间件管道:一个 HTTP 请求经过鉴权→限流→日志→业务处理,每一层都可以决定"放行"还是"拦截"。

结构图

代码实现

java代码

python代码

七、迭代器模式(Iterator)

一句话

提供一种方法顺序访问集合中的元素,而不暴露集合的内部表示。

生活中的例子

  • 翻书:你不需要知道书是怎么装订的,只需要"翻下一页"就能看到所有内容。
  • 电视遥控器翻台:按"下一个频道"就行,不用管电视信号怎么存储的。
  • 音乐播放列表:点"下一首",不用管歌曲是存在数组里还是链表里。

结构图