行为型模式
开篇:行为型模式解决的是"协作"问题
前两篇我们学了怎样创建对象(创建型),怎样组合对象(结构型)。这一篇要解决的终极问题是:多个对象之间怎样高效地分工协作?
一个软件系统里,对象之间的交互往往比对象本身更复杂。行为型模式关注的就是对象之间的职责划分和通信方式——谁干什么活,谁通知谁,按什么顺序来。
生活中的类比:
- 一家餐厅——服务员记下你的菜单(命令模式),传给厨房,厨房按照固定流程(模板方法)做菜,做好后通知服务员上菜(观察者模式)
- 一场足球赛——教练赛前制定了进攻策略和防守策略(策略模式),比赛中根据比分切换策略,换人请求沿着"球员→队长→教练→裁判"的链条传递(责任链模式)
- 一个电商订单——从"待付款"到"已付款"到"已发货"到"已签收",每个状态下能执行的操作完全不一样(状态模式)
行为型模式有 11 种之多,是三大分类中数量最多的。本篇重点讲解最常用的五种(策略、观察者、模板方法、责任链、状态),帮你掌握最核心的设计思想。
一、策略模式
什么是策略?
生活类比:你出门上班有多种交通方式——开车、坐地铁、骑自行车。每种方式的"算法"不同(路线、耗时、费用都不一样),但目的相同(把你送到公司)。你可以根据天气、时间、心情灵活选择,随时切换。
在代码中,策略模式的作用是:把一系列算法封装成独立的类,让它们可以互相替换,而不影响使用它们的客户端。
最大的好处是消灭 if-else。
从一个反面案例说起
假设你在做一个支付系统,支持支付宝、微信、银行卡三种方式:
// if-else 地狱——每种支付方式 50 行逻辑堆在一起
public void pay(String type, BigDecimal amount) {
if ("alipay".equals(type)) {
// 支付宝支付逻辑...
} else if ("wechat".equals(type)) {
// 微信支付逻辑...
} else if ("bankcard".equals(type)) {
// 银行卡支付逻辑...
}
// 以后加信用卡?加数字货币?每次都在这个方法里堆代码...
}问题很明显:方法越来越长,每次新增渠道都要改这个类,违反了开闭原则和单一职责。
用策略模式重构
// 1. 定义策略接口
public interface PayStrategy {
void pay(BigDecimal amount);
}
// 2. 实现具体策略——每种支付方式一个类
@Component("alipayPayStrategy")
public class AlipayStrategy implements PayStrategy {
public void pay(BigDecimal amount) {
System.out.println("支付宝支付: " + amount + " 元");
// 调支付宝 SDK...
}
}
@Component("wechatPayStrategy")
public class WechatStrategy implements PayStrategy {
public void pay(BigDecimal amount) {
System.out.println("微信支付: " + amount + " 元");
// 调微信 SDK...
}
}
// 3. 策略工厂(结合 Spring 的自动注入)
@Component
public class PayStrategyFactory {
@Autowired
private Map<String, PayStrategy> strategyMap; // Spring 自动注入所有实现
public PayStrategy getStrategy(String type) {
return strategyMap.get(type + "PayStrategy");
}
}
// 4. 使用——干净清爽,零 if-else
@Service
public class PayService {
@Autowired
private PayStrategyFactory factory;
public void pay(String type, BigDecimal amount) {
factory.getStrategy(type).pay(amount);
}
}新增支付渠道?只需要写一个新的策略类,标上 @Component,不改任何已有代码。完美遵守开闭原则。
策略 + 模板方法 + 工厂的经典组合
在实际项目中,不同策略通常有公共逻辑(比如参数校验、日志记录、后置处理),可以把它们抽到一个抽象类中,用模板方法实现复用:
public abstract class AbstractPayStrategy implements PayStrategy {
// 模板方法:定义公共流程
public void pay(BigDecimal amount) {
validate(amount); // 公共步骤:参数校验
doPay(amount); // 各策略自己实现:核心支付逻辑
recordLog(amount); // 公共步骤:记录日志
}
protected abstract void doPay(BigDecimal amount); // 留给子类
private void validate(BigDecimal amount) { /* 校验金额 */ }
private void recordLog(BigDecimal amount) { /* 记录日志 */ }
}
// 具体策略只需实现核心逻辑
@Component("alipayPayStrategy")
public class AlipayStrategy extends AbstractPayStrategy {
protected void doPay(BigDecimal amount) {
// 只写支付宝特有的逻辑
}
}这就是策略 + 模板方法 + 工厂的三件套,堪称工作中最常用的设计模式组合。
二、观察者模式
什么是观察者?
生活类比:你关注了一个 B 站 UP 主。UP 主发了新视频,你会收到推送通知。你不需要每隔五分钟去刷一次他的主页来检查有没有新视频——这就是"推"模式,也就是观察者模式。
在代码中,观察者模式定义了一种一对多的依赖关系:当"被观察者"(Subject)的状态发生变化时,所有"观察者"(Observer)都会自动收到通知并执行相应操作。
代码实现
// 被观察者(主题)
public class UPZhu {
private List<Observer> fans = new ArrayList<>(); // 观察者列表
public void addFan(Observer fan) { fans.add(fan); }
public void removeFan(Observer fan) { fans.remove(fan); }
// 发布视频——通知所有粉丝
public void publishVideo(String title) {
System.out.println("UP主发布视频: " + title);
for (Observer fan : fans) {
fan.onNotify("新视频上线: " + title);
}
}
}
// 观察者接口
public interface Observer {
void onNotify(String message);
}
// 真实粉丝
public class RealFan implements Observer {
private String name;
public RealFan(String name) { this.name = name; }
public void onNotify(String message) {
System.out.println(name + " 收到推送: " + message);
}
}
// 机器人粉丝
public class BotFan implements Observer {
public void onNotify(String message) {
System.out.println("[机器人] 自动点赞并收藏");
}
}
// 使用
UPZhu up = new UPZhu();
up.addFan(new RealFan("张三"));
up.addFan(new RealFan("李四"));
up.addFan(new BotFan());
up.publishVideo("Java 设计模式详解");
// 张三 收到推送: 新视频上线: Java 设计模式详解
// 李四 收到推送: 新视频上线: Java 设计模式详解
// [机器人] 自动点赞并收藏观察者模式的核心优势
松耦合:UP 主不需要知道粉丝是真人还是机器人,只管遍历列表发通知。新增粉丝类型不需要改 UP 主的任何代码。
实际项目中的观察者
- Spring 事件机制:
ApplicationEventPublisher.publishEvent()+@EventListener——这是 Spring 对观察者模式的官方封装 - 消息队列:Kafka、RocketMQ 的发布/订阅模型本质上就是观察者模式的分布式版本
- GUI 框架:按钮的点击事件监听器(
button.addActionListener()) - 前端框架:Vue 的响应式数据绑定、React 的 state 变化触发 re-render
三、模板方法模式
什么是模板方法?
生活类比:自动炒菜机有固定的流程——热锅、放油、放食材、调味、翻炒、出锅。这个"流程骨架"是不变的,但具体"放什么食材、放多少调味料"由你来设定。机器管流程,你管细节。
在代码中,模板方法就是在父类中定义算法的骨架(步骤的执行顺序),把某些步骤的具体实现延迟到子类。
// 抽象类:定义做菜的固定骨架
public abstract class CookTemplate {
// 模板方法——用 final 防止子类篡改流程顺序
public final void cook() {
heatOil(); // 步骤1:固定
addFood(); // 步骤2:子类决定
addSeasoning(); // 步骤3:子类决定
stirFry(); // 步骤4:固定
serve(); // 步骤5:固定
}
private void heatOil() { System.out.println("1. 热锅放油"); }
protected abstract void addFood(); // 子类实现
protected abstract void addSeasoning(); // 子类实现
private void stirFry() { System.out.println("4. 翻炒中..."); }
private void serve() { System.out.println("5. 装盘出锅"); }
}
// 番茄炒蛋
public class TomatoEgg extends CookTemplate {
protected void addFood() { System.out.println("2. 放入番茄和鸡蛋"); }
protected void addSeasoning() { System.out.println("3. 加盐、加糖"); }
}
// 青椒肉丝
public class PepperPork extends CookTemplate {
protected void addFood() { System.out.println("2. 放入青椒和肉丝"); }
protected void addSeasoning() { System.out.println("3. 加盐、加酱油、加蒜"); }
}
// 使用
new TomatoEgg().cook();
// 1. 热锅放油 → 2. 放入番茄和鸡蛋 → 3. 加盐、加糖 → 4. 翻炒中... → 5. 装盘出锅模板方法的关键点
- 模板方法本身用
final修饰——防止子类改变流程骨架 - 固定步骤在父类中实现——子类继承就能复用
- 可变步骤声明为
abstract——强制子类实现
模板方法 vs 策略模式
| 模板方法 | 策略模式 | |
|---|---|---|
| 实现方式 | 继承(子类覆写父类方法) | 组合(注入不同策略对象) |
| 控制权 | 父类控制流程,子类填细节 | 客户端选择策略 |
| 适用场景 | 流程固定、某些步骤需要定制 | 多种算法可以完全互相替换 |
| 灵活性 | 较低(继承是编译时绑定) | 较高(组合是运行时绑定) |
实际项目中两者经常配合使用——策略模式的抽象策略类用模板方法来复用公共流程。
JDK/Spring 中的模板方法
AbstractList:定义了get()、size()等抽象方法,ArrayList、LinkedList分别实现HttpServlet:service()方法分派到doGet()、doPost()等钩子方法- Spring
JdbcTemplate:封装了获取连接 → 执行 SQL → 处理结果 → 关闭连接的流程,你只需提供 SQL 和行映射逻辑 - Spring
AbstractApplicationContext.refresh():定义了容器启动的 12 个步骤
四、责任链模式
什么是责任链?
生活类比:你在公司请假。1 天以内组长就能批,3 天以内需要经理批,一周以上得总监批。你只需要把请假单交给组长——如果组长能批就直接批了,批不了他会自动传给经理,经理批不了再传给总监。你不需要知道最终是谁批的。
在代码中,责任链模式将多个处理者连成一条链,请求沿着链传递,每个处理者要么自己处理,要么转给下一个。
代码实现
// 抽象处理者
public abstract class Approver {
protected Approver next; // 链条中的下一个
public Approver setNext(Approver next) {
this.next = next;
return next; // 返回 next,方便链式构建
}
public abstract void approve(int days);
}
// 组长:处理 1 天以内
public class TeamLeader extends Approver {
public void approve(int days) {
if (days <= 1) {
System.out.println("组长批准: " + days + "天假");
} else if (next != null) {
next.approve(days);
} else {
System.out.println("没有人能审批 " + days + " 天的假");
}
}
}
// 经理:处理 3 天以内
public class Manager extends Approver {
public void approve(int days) {
if (days <= 3) {
System.out.println("经理批准: " + days + "天假");
} else if (next != null) {
next.approve(days);
}
}
}
// 总监:处理所有
public class Director extends Approver {
public void approve(int days) {
System.out.println("总监批准: " + days + "天假");
}
}
// 构建责任链
Approver chain = new TeamLeader();
chain.setNext(new Manager()).setNext(new Director());
// 发起请求——只找链头
chain.approve(1); // 组长批准: 1天假
chain.approve(3); // 经理批准: 3天假
chain.approve(7); // 总监批准: 7天假Servlet Filter 中的回旋责任链
Web 开发中最经典的责任链是 Servlet Filter。它的特殊之处在于是双向的——请求沿链"进去",响应沿链"出来"(像一个 U 型管道)。
每个 Filter 通过 chain.doFilter(request, response) 把请求传给下一个。当最后一个 Filter 调用完 Servlet 后,控制权按照调用栈的顺序返回每个 Filter 的 doFilter() 方法的后半部分——这就是为什么你能在 Filter 里写"请求前处理"和"请求后处理"。
Spring 的 HandlerInterceptor(preHandle → postHandle → afterCompletion)也是同样的思路。
责任链的优势
- 解耦:请求发送者不需要知道具体由谁处理,也不需要知道链条里有几个处理者
- 可动态调整:链条的节点和顺序可以在运行时改变
- 符合开闭原则:新增处理者只需加入链条,不修改已有处理者
五、状态模式
什么是状态模式?
生活类比:你是一个电竞选手。放假状态下你不想打游戏,饥饿状态下你打得很差,吃饱睡好之后全力以赴才能打出最佳水平。你的"状态"不同,"行为"就完全不同。而且状态之间有明确的流转关系:放假 → 饥饿 → 吃饱 → 比赛 → 放假...
在代码中,状态模式让一个对象在内部状态变化时改变它的行为,看起来就像改变了类一样。
经典场景:订单状态机
// 状态接口
public interface OrderState {
void next(Order order); // 推进到下一个状态
void printStatus(); // 打印当前状态信息
}
// 状态1:已创建
public class CreatedState implements OrderState {
public void next(Order order) {
System.out.println("用户完成付款");
order.setState(new PaidState());
}
public void printStatus() { System.out.println("当前状态: 待付款"); }
}
// 状态2:已支付
public class PaidState implements OrderState {
public void next(Order order) {
System.out.println("商家已发货");
order.setState(new ShippedState());
}
public void printStatus() { System.out.println("当前状态: 待发货"); }
}
// 状态3:已发货
public class ShippedState implements OrderState {
public void next(Order order) {
System.out.println("用户确认签收");
order.setState(new CompletedState());
}
public void printStatus() { System.out.println("当前状态: 运输中"); }
}
// 状态4:已完成(终态)
public class CompletedState implements OrderState {
public void next(Order order) {
System.out.println("订单已完成,无法继续推进");
}
public void printStatus() { System.out.println("当前状态: 已完成"); }
}
// 上下文:订单对象
public class Order {
private OrderState state;
public Order() { this.state = new CreatedState(); }
public void setState(OrderState state) { this.state = state; }
public void nextStep() { state.next(this); }
public void printStatus() { state.printStatus(); }
}
// 使用
Order order = new Order();
order.printStatus(); // 当前状态: 待付款
order.nextStep(); // 用户完成付款
order.printStatus(); // 当前状态: 待发货
order.nextStep(); // 商家已发货
order.printStatus(); // 当前状态: 运输中
order.nextStep(); // 用户确认签收
order.printStatus(); // 当前状态: 已完成状态模式的核心价值
如果不用状态模式,订单的所有状态逻辑会集中在一个巨大的 switch-case 里。每个方法开头都要判断当前状态,然后决定能不能执行、执行后变成什么状态。代码极其臃肿且容易出错。
状态模式把每个状态的行为封装在独立的类中,状态流转的逻辑也在状态类内部完成。新增状态只需加一个类,不用改已有状态的代码。
状态模式 vs 策略模式
这两个模式的类图几乎相同,面试中经常被问到区别:
| 状态模式 | 策略模式 | |
|---|---|---|
| 关注点 | 对象在不同状态下的行为差异 | 客户端主动选择算法 |
| 切换方式 | 状态内部自动流转(下一个状态由当前状态决定) | 客户端从外部显式指定策略 |
| 对象是否感知 | 对象知道自己有状态,状态驱动行为 | 对象不关心当前是哪个策略 |
| 典型场景 | 订单流转、审批流、角色状态 | 支付方式选择、排序算法、折扣计算 |
简单记忆:策略是"我选哪个算法",状态是"我在哪个阶段"。
六、常见面试题精选
Q1:策略模式和 if-else 相比有什么好处?
if-else 把所有分支逻辑堆在一个方法里,每新增一种情况就要修改这个方法,违反开闭原则和单一职责。策略模式把每种算法封装成独立的类,新增算法只需新增类,完全不改已有代码。从可读性看,每个策略类职责清晰、代码量小;从可测试性看,可以单独对每个策略写单元测试;从可复用性看,同一个策略可以被多个客户端共享。实际项目中通常结合工厂模式来管理策略实例,结合模板方法来复用公共逻辑。
Q2:什么是观察者模式?有哪些实际应用?
观察者模式定义了一对多的依赖关系。被观察者(Subject)维护一个观察者列表,当状态变化时遍历列表通知所有观察者。核心优势是松耦合——被观察者不需要知道观察者是谁、有多少个、是什么类型。典型应用包括:Spring 的事件机制(ApplicationEventPublisher + @EventListener)、消息队列的发布/订阅模型(Kafka、RocketMQ)、GUI 框架的事件监听器、前端 MVVM 框架中数据变化驱动视图更新。
Q3:什么是模板方法?和策略模式有什么区别?
模板方法在父类中定义算法骨架,将可变步骤延迟到子类实现。核心是"固定流程,可变细节"。和策略模式的本质区别在于实现机制:模板方法基于继承(编译时绑定),策略基于组合(运行时绑定)。模板方法适合"流程固定但某些步骤需定制"的场景,策略适合"多种算法完全可互换"的场景。实际项目中两者经常组合使用——策略类的抽象基类用模板方法复用公共流程。
Q4:什么是责任链模式?和 if-else 有什么区别?
责任链把多个处理者连成链条,请求沿链传递,每个处理者决定自己是否处理、是否继续传递。和 if-else 的本质区别是解耦——if-else 把所有判断逻辑耦合在一起,新增条件必须修改已有代码;责任链中每个处理者是独立的类,新增处理者只需加入链条即可。而且链条的顺序可以在运行时动态调整。经典应用有 Servlet Filter、Spring Interceptor、Netty Pipeline、OkHttp Interceptor。
七、其他行为型模式速览
虽然本篇重点讲了五种核心模式,但其余六种在特定场景下同样重要,这里做一个简要速览,让你在遇到相关问题时知道该去找哪种模式。
命令模式
核心思想:把一个请求封装成一个对象,这样你就可以用不同的请求来参数化其他对象,而且还能支持撤销操作。
生活类比:电视遥控器。你按下"换台"按钮,遥控器把这个"换台命令"发给电视机。你按下"音量+"按钮,发出另一个命令。按钮(调用者)和电视机(执行者)之间是解耦的——按钮不知道电视机内部怎么换台。
// 命令接口
public interface Command {
void execute();
void undo(); // 支持撤销
}
// 具体命令
public class VolumeUpCommand implements Command {
private TV tv;
public VolumeUpCommand(TV tv) { this.tv = tv; }
public void execute() { tv.volumeUp(); }
public void undo() { tv.volumeDown(); }
}
// 遥控器(调用者)
public class RemoteControl {
private Stack<Command> history = new Stack<>();
public void press(Command cmd) {
cmd.execute();
history.push(cmd);
}
public void undoLast() {
if (!history.isEmpty()) history.pop().undo();
}
}典型应用:文本编辑器的撤销/重做、任务队列、宏命令(批量执行)。
中介者模式
核心思想:用一个中间人来管理多个对象之间的交互,避免对象之间两两直接引用形成复杂的网状结构。
生活类比:飞机场的塔台。所有飞机的起降请求都发给塔台,塔台统一调度。飞机之间不需要互相通信。
典型应用:MVC 中的 Controller 就是 Model 和 View 之间的中介者、聊天室服务器。
迭代器模式
核心思想:提供一种统一的方式来遍历一个集合中的元素,而不暴露集合的内部结构。
Java 的 Iterator 接口就是迭代器模式的标准实现。你可以用同一个 for-each 循环遍历 ArrayList、LinkedList、HashSet——虽然它们内部结构完全不同。
访问者模式
核心思想:在不修改数据结构的前提下,新增对数据的操作。把"操作"抽取出来放到独立的访问者类中。
适用场景:数据结构稳定但操作经常变化的场景。ASM 字节码框架的 ClassVisitor 就是典型的访问者模式。
备忘录模式
核心思想:在不破坏封装的前提下,保存一个对象的内部状态快照,以便将来恢复。
生活类比:游戏存档。你打 Boss 前存一个档,打输了可以读档重来。
解释器模式
核心思想:给一种语言定义文法,然后构建解释器来解释这种语言中的句子。
适用场景比较窄,典型例子是正则表达式引擎、EL 表达式解析、SQL 解析器。
八、实战:天气变化触发通知和推荐——观察者 + 策略的组合
我们用一个完整案例来体会两种模式的协作。场景:天气变化时通知用户,并根据不同天气推荐不同行程。
// 1. 策略接口——不同天气的推荐策略
public interface RecommendStrategy {
String recommend();
}
public class SunnyStrategy implements RecommendStrategy {
public String recommend() { return "推荐户外活动:公园野餐、骑行"; }
}
public class RainyStrategy implements RecommendStrategy {
public String recommend() { return "推荐室内活动:看电影、逛商场"; }
}
// 2. 观察者接口
public interface WeatherObserver {
void onWeatherChange(String weather);
}
// 3. 通知服务(观察者之一)
public class NotificationService implements WeatherObserver {
public void onWeatherChange(String weather) {
System.out.println("[通知] 天气变为: " + weather);
}
}
// 4. 推荐服务(观察者之二 + 使用策略模式)
public class RecommendService implements WeatherObserver {
private Map<String, RecommendStrategy> strategies = Map.of(
"晴天", new SunnyStrategy(),
"雨天", new RainyStrategy()
);
public void onWeatherChange(String weather) {
RecommendStrategy strategy = strategies.getOrDefault(weather, () -> "暂无推荐");
System.out.println("[推荐] " + strategy.recommend());
}
}
// 5. 天气服务(被观察者)
public class WeatherService {
private List<WeatherObserver> observers = new ArrayList<>();
public void addObserver(WeatherObserver o) { observers.add(o); }
public void changeWeather(String weather) {
System.out.println("====== 天气变化: " + weather + " ======");
observers.forEach(o -> o.onWeatherChange(weather));
}
}
// 使用
WeatherService ws = new WeatherService();
ws.addObserver(new NotificationService());
ws.addObserver(new RecommendService());
ws.changeWeather("晴天");
ws.changeWeather("雨天");这个案例完美展示了**观察者负责"何时通知",策略负责"如何处理"**的分工协作。
小结
行为型模式的核心理念是合理分配职责,让对象之间的协作既松耦合又高效:
| 模式 | 核心问题 | 生活类比 | 经典应用 |
|---|---|---|---|
| 策略 | 多种算法可互换,消灭 if-else | 上班选交通工具 | 支付渠道、折扣计算 |
| 观察者 | 状态变化时一对多通知 | 关注 UP 主收推送 | Spring Event、MQ |
| 模板方法 | 固定骨架,可变步骤 | 自动炒菜机 | JdbcTemplate、HttpServlet |
| 责任链 | 请求沿链传递,逐级处理 | 请假逐级审批 | Servlet Filter、Spring Interceptor |
| 状态 | 状态驱动行为变化 | 订单状态流转 | 订单状态机、审批流 |
除了这五种核心模式,行为型还有命令模式(请求封装成对象,支持撤销/重做)、中介者模式(用一个中间人集中管理对象交互)、迭代器模式(统一的遍历接口)、访问者模式(在不改结构的前提下新增操作)、备忘录模式(保存和恢复对象状态)、解释器模式(定义语法并解释执行)等,它们在特定场景下各有用武之地。
下一篇进入总结与问答,我们会从更宏观的角度回顾所有 23 种模式在 Spring 框架和 JDK 中的实际应用。