Skip to content

元数据卡

  • 前置知识:第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 的协同使用

破局 · 溯源

你的擂台赛后要执行一系列操作:更新积分、更新排行榜、发通知、记录日志、更新成就系统。你写了一个大函数:

java
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 不需要知道观察者是谁、有多少。

java
// 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("检查成就");
    }
}

使用:

java
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 模式把它拆成了独立的算法类:

java
// 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);
    }
}

使用:

java
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 版本更简洁——函数本身就是策略:

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 在父类定义算法的骨架,子类实现(覆盖)其中的特定步骤。

java
// 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——请求的传递链

你的赛事审核系统需要多级审批:普通审核 -> 队长审核 -> 管理员审核。每个审核者可以处理或传给下一个。

java
// 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;
    }
}

使用:

java
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 模式把"请求"封装为一个对象,这样你就可以存储请求、延迟执行、回退重做。

java
// 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();
        }
    }
}

使用:

java
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,而是把每个状态变成独立的类:

java
// 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-eachIterable 接口就是 Iterator 模式。Python 的 __iter____next__ 同理。

java
for (Match m : tournament.getMatches()) { /* ... */ }
// tournament.getMatches() 内部可能是数组、List 或 Set,调用方不知道也不关心

Mediator(中介者):对象之间不直接通信,而是通过中介者协调。

java
// 玩家不直接发消息给另一个玩家,而是通过聊天室(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 的状态,中途可以读档。

java
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() 方法。

java
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
// 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 集成。

Built with VitePress | Software Systems Atlas