元数据卡
- 前置知识:第7章(设计原则);第8-9章(创建型、结构型模式)
- 预计时间:60 分钟
- 核心难度:深入
- 完成标志:能识别 11 种行为型模式的适用场景,在项目中正确应用
你的进度
你能造零件、组装零件。但机器运转起来后,零件之间需要协同——哪个齿轮先动、哪个弹簧什么时候释放、哪个杠杆在什么条件下触发。
你拆开一台老机器,里面有 5 个嵌套的控制杆、3 个互相牵制的触发器。你看了半天不敢碰:“这谁设计的?”翻开工坊手册——你三年前画的。 你的任务
行为型模式关注对象之间的通信。11 种模式各对应一种常见的协作场景:责任链把多个处理器串起来、命令把请求封装为对象、观察者让对象监听另一个对象、策略让算法可替换、状态让行为随状态变化、模板方法定义算法骨架、迭代器统一访问集合、中介者让对象不直接通信、备忘录保存和恢复状态、访问者在不改类的前提下添加操作、解释器解析语法。
本章分层
- 必读:Observer、Strategy、Template Method、Chain of Responsibility
- 选读:Command、State、Iterator
- 进阶:Mediator、Memento、Visitor、Interpreter
本章不会要求你掌握
- Interpreter 的 LLVM 级别实现
- 组合模式与 Visitor 的协同使用
破局 · 溯源
你的擂台赛后要执行一系列操作:更新积分、更新排行榜、发通知、记录日志、更新成就系统。你写了一个大函数:
public void finishMatch(Match match) {
scoreService.update(match);
leaderboardService.refresh();
notificationService.sendToPlayers(match);
logService.record(match);
achievementService.check(match);
}这个大函数的问题是:每次赛后加一个新操作,你都要改这个函数。 而且测试必须把 5 个服务全部 mock。简单的事件"比赛结束",却触发了硬编码的耦合。
你需要一种方式让"事件"和"响应"解耦。Observer 就是答案。
第一层:Observer——一对多的通知机制
Observer 让一个对象(Subject)在状态变化时自动通知多个观察者(Observers)。Subject 不需要知道观察者是谁、有多少。
// ch10/observer/EventBus.java
// 事件
public class MatchFinishedEvent {
private final String matchId;
private final String winner;
private final int score;
public MatchFinishedEvent(String matchId, String winner, int score) {
this.matchId = matchId;
this.winner = winner;
this.score = score;
}
// getters...
}
// 观察者接口
public interface MatchObserver {
void onMatchFinished(MatchFinishedEvent event);
}
// Subject:比赛管理器
public class MatchManager {
private final List<MatchObserver> observers = new ArrayList<>();
public void addObserver(MatchObserver observer) {
observers.add(observer);
}
public void removeObserver(MatchObserver observer) {
observers.remove(observer);
}
public void finishMatch(Match match, String winner) {
// ... 比赛结束逻辑 ...
// 通知所有观察者
MatchFinishedEvent event = new MatchFinishedEvent(
match.getId(), winner, match.getScore());
for (MatchObserver observer : observers) {
observer.onMatchFinished(event);
}
}
}
// 具体观察者
public class ScoreObserver implements MatchObserver {
@Override
public void onMatchFinished(MatchFinishedEvent event) {
System.out.println("更新积分:玩家 " + event.getWinner());
}
}
public class LeaderboardObserver implements MatchObserver {
@Override
public void onMatchFinished(MatchFinishedEvent event) {
System.out.println("刷新排行榜");
}
}
public class AchievementObserver implements MatchObserver {
@Override
public void onMatchFinished(MatchFinishedEvent event) {
System.out.println("检查成就");
}
}使用:
MatchManager manager = new MatchManager();
manager.addObserver(new ScoreObserver());
manager.addObserver(new LeaderboardObserver());
manager.addObserver(new AchievementObserver());
manager.finishMatch(match, "steven");
// 输出:
// 更新积分:玩家 steven
// 刷新排行榜
// 检查成就现在,赛后新增一个操作(积分发放给工会),你只需要写一个新的 MatchObserver 实现注册到 MatchManager——不需要修改 finishMatch 方法。
在更实际的系统中,你可能会用事件总线或消息队列替代手动管理观察者列表:Spring 的 @EventListener、Guava 的 EventBus。
第二层:Strategy——可互换的算法族
你的奖品计算逻辑有多个变体:普通赛、排位赛、限时赛、锦标赛——每种算分方式不同。
用 if-else 可以解决,但 Strategy 模式把它拆成了独立的算法类:
// ch10/strategy/ScoringStrategy.java
public interface ScoringStrategy {
int calculate(int baseScore, Match match);
}
public class NormalScoring implements ScoringStrategy {
@Override
public int calculate(int baseScore, Match match) {
return baseScore;
}
}
public class RankedScoring implements ScoringStrategy {
@Override
public int calculate(int baseScore, Match match) {
return baseScore * match.getRankMultiplier();
}
}
public class TimeLimitedScoring implements ScoringStrategy {
@Override
public int calculate(int baseScore, Match match) {
return (int) (baseScore * (1 + match.getRemainingTime() / 60.0));
}
}
// 上下文
public class MatchScoringContext {
private ScoringStrategy strategy;
public void setStrategy(ScoringStrategy strategy) {
this.strategy = strategy;
}
public int score(Match match) {
return strategy.calculate(match.getBaseScore(), match);
}
}使用:
MatchScoringContext context = new MatchScoringContext();
context.setStrategy(new RankedScoring());
int score = context.score(match); // 排位赛计分
context.setStrategy(new TimeLimitedScoring());
score = context.score(match); // 限时赛计分Strategy 的核心价值:算法替换不影响调用方。新增一个"团队赛计分"写法一模一样——新类 TeamScoring implements ScoringStrategy,不用改 MatchScoringContext。
Python 版本更简洁——函数本身就是策略:
# ch10/strategy/scoring.py
from typing import Callable
# 策略就是函数
def normal_scoring(base_score: int, match) -> int:
return base_score
def ranked_scoring(base_score: int, match) -> int:
return base_score * match.rank_multiplier
# 上下文接受策略函数
class MatchScoringContext:
def __init__(self, strategy: Callable):
self._strategy = strategy
def score(self, match) -> int:
return self._strategy(match.base_score, match)第三层:Template Method——算法骨架,子类实现细节
Arena 比赛和 Puzzle 比赛都包含"开始 -> (等待) -> 结束 -> 发奖"四个步骤,但"等待"阶段的规则不同。
Template Method 在父类定义算法的骨架,子类实现(覆盖)其中的特定步骤。
// ch10/template/GameSession.java
public abstract class GameSession {
// 模板方法:定义算法骨架(final 阻止子类重写)
public final void run() {
start();
waitForParticipants();
runGame();
finish();
awardPrizes();
}
// 公共步骤:所有子类相同
private void start() {
System.out.println("游戏会话开始");
}
// 抽象步骤:子类实现
protected abstract void waitForParticipants();
protected abstract void runGame();
// 钩子方法:子类可选覆盖
protected void finish() {
System.out.println("默认结束流程");
}
private void awardPrizes() {
System.out.println("发放奖励");
}
}
// 擂台赛
public class ArenaSession extends GameSession {
@Override
protected void waitForParticipants() {
System.out.println("等待两名选手到齐");
}
@Override
protected void runGame() {
System.out.println("3...2...1... 战斗开始!");
}
}
// 解谜赛
public class PuzzleSession extends GameSession {
@Override
protected void waitForParticipants() {
System.out.println("等待所有队伍到齐");
}
@Override
protected void runGame() {
System.out.println("谜题已分发,限时 30 分钟");
}
@Override
protected void finish() {
System.out.println("解谜赛结束——公布答案");
}
}Template Method 和 Strategy 的区别:Template Method 是用继承改变算法的特定步骤;Strategy 是用组合替换整个算法。Template Method 适合"大部分流程不变,只变其中几个环节"的场景。
第四层:Chain of Responsibility——请求的传递链
你的赛事审核系统需要多级审批:普通审核 -> 队长审核 -> 管理员审核。每个审核者可以处理或传给下一个。
// ch10/chain/ReviewHandler.java
public abstract class ReviewHandler {
protected ReviewHandler next;
public ReviewHandler setNext(ReviewHandler next) {
this.next = next;
return next;
}
public abstract boolean handle(Tournament tournament);
}
public class BasicReviewHandler extends ReviewHandler {
@Override
public boolean handle(Tournament tournament) {
if (tournament.getMaxPlayers() <= 8) {
System.out.println("普通审核:无需审批");
return true;
}
return next != null && next.handle(tournament);
}
}
public class CaptainReviewHandler extends ReviewHandler {
@Override
public boolean handle(Tournament tournament) {
if (tournament.getMaxPlayers() <= 32) {
System.out.println("队长审核:批准");
return true;
}
return next != null && next.handle(tournament);
}
}
public class AdminReviewHandler extends ReviewHandler {
@Override
public boolean handle(Tournament tournament) {
if (tournament.getMaxPlayers() <= 100) {
System.out.println("管理员审核:批准");
return true;
}
System.out.println("管理员审核:拒绝(超过容量)");
return false;
}
}使用:
ReviewHandler chain = new BasicReviewHandler();
chain.setNext(new CaptainReviewHandler())
.setNext(new AdminReviewHandler());
chain.handle(new Tournament("小赛", 4)); // 普通审核
chain.handle(new Tournament("大赛", 50)); // 管理员审核责任链模式在框架中广泛使用:Spring Security 的 FilterChain、Java Servlet 的 Filter、中间件(Express/Koa)都基于这个模式。
第五层:Command——把请求封装为对象
你的游戏需要一个"撤销操作"功能:玩家不小心用了一个道具,要回退。直接调用的函数调用是不可回退的——执行了就没了。
Command 模式把"请求"封装为一个对象,这样你就可以存储请求、延迟执行、回退重做。
// ch10/command/Command.java
public interface Command {
void execute();
void undo();
}
public class UseItemCommand implements Command {
private final Player player;
private final Item item;
private final int quantity;
public UseItemCommand(Player player, Item item, int quantity) {
this.player = player;
this.item = item;
this.quantity = quantity;
}
@Override
public void execute() {
player.removeItem(item, quantity);
player.applyEffect(item.getEffect());
}
@Override
public void undo() {
player.addItem(item, quantity);
player.removeEffect(item.getEffect());
}
}
// 调用者(Invoker):管理命令历史
public class CommandHistory {
private final Deque<Command> history = new ArrayDeque<>();
public void execute(Command cmd) {
cmd.execute();
history.push(cmd);
}
public void undo() {
if (!history.isEmpty()) {
Command cmd = history.pop();
cmd.undo();
}
}
}使用:
CommandHistory history = new CommandHistory();
history.execute(new UseItemCommand(player, healingPotion, 1));
// 玩家用了药水,HP 回复
history.undo();
// 回到用药水前的状态——药水回到背包,HP 回到之前的值第六层:State——状态决定行为
一个 Match 有多个状态:READY、IN_PROGRESS、COMPLETED、CANCELLED。不同状态下同一方法的行为不同——例如 start() 在 READY 下执行,在 IN_PROGRESS 下抛异常。
处理办法:不在 Match 类里写 if-else,而是把每个状态变成独立的类:
// ch10/state/MatchState.java
public interface MatchState {
void start(Match context);
void complete(Match context, String winner);
void cancel(Match context);
String getStatusName();
}
public class ReadyState implements MatchState {
@Override
public void start(Match context) {
System.out.println("比赛开始");
context.setState(new InProgressState());
}
@Override
public void complete(Match context, String winner) {
throw new IllegalStateException("比赛还未开始");
}
@Override
public void cancel(Match context) {
System.out.println("比赛已取消");
context.setState(new CancelledState());
}
@Override
public String getStatusName() { return "READY"; }
}
public class InProgressState implements MatchState {
@Override
public void start(Match context) {
throw new IllegalStateException("比赛已经开始了");
}
@Override
public void complete(Match context, String winner) {
System.out.println("比赛结束,获胜者: " + winner);
context.setWinner(winner);
context.setState(new CompletedState());
}
@Override
public void cancel(Match context) {
System.out.println("比赛中断取消");
context.setState(new CancelledState());
}
@Override
public String getStatusName() { return "IN_PROGRESS"; }
}
// Match 上下文
public class Match {
private MatchState state = new ReadyState();
public void setState(MatchState state) { this.state = state; }
public void start() { state.start(this); }
public void complete(String winner) { state.complete(this, winner); }
public void cancel() { state.cancel(this); }
public String getStatus() { return state.getStatusName(); }
}状态模式的核心思想:把状态转换逻辑从庞杂的 if-else 中解放出来,分散到各个状态类中,每新增一个状态只需新增一个类。
第七到十一层:迭代器、中介者、备忘录、访问者、解释器
Iterator(迭代器):让集合的遍历与集合的实现分离。Java 的 for-each 和 Iterable 接口就是 Iterator 模式。Python 的 __iter__ 和 __next__ 同理。
for (Match m : tournament.getMatches()) { /* ... */ }
// tournament.getMatches() 内部可能是数组、List 或 Set,调用方不知道也不关心Mediator(中介者):对象之间不直接通信,而是通过中介者协调。
// 玩家不直接发消息给另一个玩家,而是通过聊天室(Mediator)
public class ChatRoom {
public void sendMessage(Player sender, String message) {
for (Player p : players) {
if (p != sender) p.receive(sender.getName(), message);
}
}
}Memento(备忘录):保存对象状态以便恢复。游戏存档就是典型的 Memento——保存 Player 的状态,中途可以读档。
public class PlayerMemento {
private final int hp;
private final int mp;
private final int score;
public PlayerMemento(int hp, int mp, int score) {
this.hp = hp; this.mp = mp; this.score = score;
}
// getters...
}Visitor(访问者):在不修改元素类的前提下,为元素添加新操作。当你的奖品系统有各种元素(武器、药水、防具),你想计算每种元素的税费——但不想在每个元素类里加 calculateTax() 方法。
public interface PrizeVisitor {
void visit(Weapon weapon);
void visit(Potion potion);
void visit(Armor armor);
}
public class TaxCalculator implements PrizeVisitor {
public double totalTax = 0;
public void visit(Weapon w) { totalTax += w.getValue() * 0.1; }
public void visit(Potion p) { totalTax += p.getValue() * 0.05; }
public void visit(Armor a) { totalTax += a.getValue() * 0.15; }
}Interpreter(解释器):定义一种语言的语法表示,然后解释句子。比如你的礼品兑换码系统,支持表达式 GOLD > 100 AND LEVEL > 10,Interpreter 可以解析和求值。
常见陷阱
陷阱一:Observer 的事件泄漏。 Observer 注册后忘记注销,导致内存泄漏。在 Java 中如果 Subject 持有 Observer 的引用,而 Observer 不释放注册。用弱引用(WeakReference)或在生命周期结束时主动 removeObserver。
陷阱二:Strategy 过度细分。 每个算法只有一行区别也拆成了独立的类。这时 Lambda 或方法引用足以充当策略:
// Java 8+ 策略用 Lambda 表达
context.setStrategy(m -> m.getBaseScore() * 2);陷阱三:责任链太长。 一条链 10 个处理器,每个只做一点点事。请求穿过所有处理器,调试时很难追踪。考虑把链拆短或合并处理器。
陷阱四:State 模式的状态类膨胀。 每个状态类大量重复代码。把公共行为提取到抽象类或使用默认方法。
陷阱五:Visitor 破坏封装。 访问者需要访问元素的内部状态才能操作——这破坏了 ADT 的封装原则。只在需要为稳定的类层次增加新操作时才用 Visitor。
通关挑战
- 热身:为你的赛事系统新增"每场比赛结束后,向所有观众推送通知"的功能。用 Observer 模式实现。
- 挑战:把一节中 match 状态的 if-else 重构为 State 模式。写测试验证所有状态转换合法。
- 进阶:用 Command 模式实现一个"回退三步"的功能——玩家误操作后可以回退最近三步操作。
旅人笔记
行为型模式回答的是"对象之间怎么说话":Observer 让监听与事件分离、Strategy 让算法可替换、Template Method 固定骨架、Chain 串联处理器、Command 封装操作、State 隔离状态行为。最终的目标一致——让代码的协作方式更清晰,改动一个地方时不牵连五个。
下一站预告
设计模式学完了。Part 2 结束。但模式是微观层面的——一个类、一组对象怎么设计。更大的问题是:整个应用的结构应该长什么样?下一部分从分层架构起步,逐步走向六边形架构、微服务迁移、数据系统模式、事件驱动和 AI 集成。