复合模式:模式之模式
当模式们开始合作
前面九节课,我们学了二十多种设计模式——创建型的工厂三兄弟、建造者、原型,行为型的策略、状态、命令、观察者、中介者、模板方法,结构型的适配器、组合、桥接、装饰、外观、享元、代理。每种模式都是一把解决特定问题的钥匙。
但现实中的软件系统很少只有一个设计问题。你可能同时面对「接口不匹配」「需要动态添加功能」「统一管理一群对象」「解耦通知机制」等多个挑战。这时候,模式不再是孤军作战,而是协同出击。
这就引出了本节的主题——
复合模式
复合模式(Compound Pattern)是将两种或多种设计模式整合在一起,形成一个能解决常见或通用问题的综合方案。
复合模式不是简单地把几个模式「堆」在一起。它要求模式之间存在有机的协作关系:一个模式解决的问题自然地引出另一个模式的应用场景,模式之间相互配合而非相互干扰。
我们接下来通过一个完整的案例——鸭子模拟器——来体验这一过程。这个案例不会一次性抛出所有模式,而是随着需求的逐步演进,每一步都自然地引入一种新模式。到最后你会发现,五六种模式在同一个系统中各司其职、浑然一体。
鸭子模拟器:从零构建
需求分析
假设我们要开发一款鸭子模拟器。先前的模拟大鹅游戏大受好评,公司决定做一个模拟鸭子的版本。需求如下:
- 能够方便地增加新种类的鸭子,如野鸭、绿头鸭、红头鸭、橡皮鸭等
- 其他禽类(如鹅)也可能混进来
- 呱呱叫学家想要统计鸭群的总叫声次数
- 需要对鸭子进行统一装配,确保行为一致
- 需要支持鸭子「家族」,单个鸭子和一族鸭子都能统一管理
- 兼具可扩展性与灵活性
这些需求看起来各不相同,但正是这种多维度的需求,催生了多种模式的协作。
起点:Quackable 接口
我们从最简单的设计开始——定义一个 Quackable 接口,所有鸭子类都实现这个接口:
1 2 3 | public interface Quackable { void quack(); } |
这个接口非常简洁:所有能「呱呱叫」的对象都实现 quack() 方法。接下来创建几个具体的鸭子类——除了真实的鸭子,还有 DuckCall(鸭鸣器,猎人用来模仿鸭叫的工具)和 RubberDuck(橡皮鸭):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class MallardDuck implements Quackable { public void quack() { System.out.println("Quack"); // 绿头鸭的标准呱呱叫 } } public class RedheadDuck implements Quackable { public void quack() { System.out.println("Quack"); // 红头鸭也是呱呱叫 } } public class DuckCall implements Quackable { public void quack() { System.out.println("Kwak"); // 鸭鸣器——模仿鸭叫的工具 } } public class RubberDuck implements Quackable { public void quack() { System.out.println("Squeak"); // 橡皮鸭的吱吱叫 } } |
有了鸭子,还需要一个模拟器来让它们叫起来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class DuckSimulator { public static void main(String[] args) { DuckSimulator simulator = new DuckSimulator(); simulator.simulate(); } void simulate() { Quackable mallardDuck = new MallardDuck(); Quackable redheadDuck = new RedheadDuck(); Quackable duckCall = new DuckCall(); Quackable rubberDuck = new RubberDuck(); System.out.println("Duck Simulator"); simulate(mallardDuck); simulate(redheadDuck); simulate(duckCall); simulate(rubberDuck); } void simulate(Quackable duck) { duck.quack(); } } |
一切正常工作。但现在,需求开始逐步涌来。
第一步:适配器模式——让鹅混入鸭群
问题
模拟器里突然闯进了一只鹅。鹅也会叫、会飞、会游泳,凭什么不能加入模拟器?但问题是,鹅不会呱呱叫——它们是咯咯叫的,它根本没有实现 Quackable 接口:
1 2 3 4 5 | public class Goose { public void honk() { System.out.println("Honk"); // 鹅的咯咯叫 } } |
模拟器只认 Quackable,鹅的 honk() 方法名字都不对。我们不想修改鹅类(它可能是第三方代码),也不想修改模拟器——我们需要的是一个「翻译官」。
解决方案
还记得适配器模式吗?它的职责就是把一个接口转换成另一个接口。我们给鹅套上一个适配器,让它看起来像一只鸭子:
1 2 3 4 5 6 7 8 9 10 11 12 | // 鹅的适配器——把 Goose 包装成 Quackable public class GooseAdapter implements Quackable { Goose goose; public GooseAdapter(Goose goose) { this.goose = goose; } public void quack() { goose.honk(); // 把 quack() 的调用委托给 honk() } } |
现在鹅可以在模拟器中和鸭子们一起工作了:
1 2 3 4 5 | void simulate() { // ... 鸭子们 ... Quackable gooseDuck = new GooseAdapter(new Goose()); simulate(gooseDuck); // 输出 "Honk" } |
classDiagram
direction LR
class Quackable {
<<interface>>
+quack()* void
}
class MallardDuck {
+quack() void
}
class GooseAdapter {
-goose : Goose
+quack() void
}
class Goose {
+honk() void
}
Quackable <|.. MallardDuck
Quackable <|.. GooseAdapter
GooseAdapter --> Goose : 委托
classDef iface fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
classDef impl fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
classDef adaptee fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
class Quackable iface
class MallardDuck impl
class GooseAdapter impl
class Goose adaptee
模式回顾:适配器模式通过组合(composition)持有被适配对象的引用,将不兼容的接口转换为目标接口。客户端(模拟器)完全不知道它面对的其实是一只鹅。
第二步:装饰模式——叫声计数
问题
呱呱叫学家对鸭子的叫声充满研究热情,他们想要统计鸭群的总叫声次数。但有一个关键约束:不能修改鸭子类。
这正是装饰模式的用武之地——在不修改原有对象的前提下,动态添加新功能。
解决方案
我们创建一个 QuackCounter 装饰器,它包裹住任何 Quackable 对象,在每次调用 quack() 时记录计数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class QuackCounter implements Quackable { Quackable duck; // 被装饰的鸭子 static int numberOfQuacks; // 静态变量——所有装饰器共享同一个计数 public QuackCounter(Quackable duck) { this.duck = duck; } public void quack() { duck.quack(); // 委托给被装饰的鸭子 numberOfQuacks++; // 计数 +1 } public static int getQuacks() { return numberOfQuacks; } } |
使用方式很直观——用 QuackCounter 包裹每一只鸭子:
1 2 3 4 5 6 7 8 9 10 11 12 13 | void simulate() { Quackable mallardDuck = new QuackCounter(new MallardDuck()); Quackable redheadDuck = new QuackCounter(new RedheadDuck()); Quackable duckCall = new QuackCounter(new DuckCall()); Quackable rubberDuck = new QuackCounter(new RubberDuck()); // 鹅不需要计数——它不是鸭子 Quackable gooseDuck = new GooseAdapter(new Goose()); simulate(mallardDuck); // ... 其他鸭子 ... System.out.println("Total quacks: " + QuackCounter.getQuacks()); } |
装饰器和被装饰对象实现同一个接口,所以对模拟器来说完全透明——它甚至不知道鸭子被包了一层。
新的隐患
叫声计数功能很棒,但存在一个严重问题——如果有人创建鸭子时忘了用 QuackCounter 包裹,那只鸭子的叫声就不会被统计。大量叫声可能因此未被准确统计。
这就是装饰模式的痛点:必须确保每个对象都被正确包装,否则就无法获得增强功能。
解决这个问题的思路是:把鸭子的创建过程集中管理——将实例化和装饰逻辑封装在一起。这不就是工厂模式吗?
第三步:抽象工厂模式——确保装饰
问题
手动创建并装饰每只鸭子,既麻烦又容易遗漏。我们需要一个「质量控制机制」来确保所有鸭子都被正确装饰。
解决方案
由于工厂需要生产一个「产品族」——各种类型的鸭子——我们采用抽象工厂模式。先定义抽象工厂:
1 2 3 4 5 6 | public abstract class AbstractDuckFactory { public abstract Quackable createMallardDuck(); public abstract Quackable createRedheadDuck(); public abstract Quackable createDuckCall(); public abstract Quackable createRubberDuck(); } |
然后分别实现两个具体工厂:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // 基础工厂——生产未经装饰的「裸」鸭子 public class DuckFactory extends AbstractDuckFactory { public Quackable createMallardDuck() { return new MallardDuck(); } public Quackable createRedheadDuck() { return new RedheadDuck(); } public Quackable createDuckCall() { return new DuckCall(); } public Quackable createRubberDuck() { return new RubberDuck(); } } // 计数工厂——每只鸭子出厂时自动包裹 QuackCounter public class CountingDuckFactory extends AbstractDuckFactory { public Quackable createMallardDuck() { return new QuackCounter(new MallardDuck()); } public Quackable createRedheadDuck() { return new QuackCounter(new RedheadDuck()); } public Quackable createDuckCall() { return new QuackCounter(new DuckCall()); } public Quackable createRubberDuck() { return new QuackCounter(new RubberDuck()); } } |
模拟器方法接收抽象工厂作为参数,通过多态决定生产哪种鸭子:
1 2 3 4 5 6 7 | void simulate(AbstractDuckFactory duckFactory) { Quackable mallardDuck = duckFactory.createMallardDuck(); Quackable redheadDuck = duckFactory.createRedheadDuck(); Quackable duckCall = duckFactory.createDuckCall(); Quackable rubberDuck = duckFactory.createRubberDuck(); // ... } |
只需传入不同的具体工厂,就能在「计数」与「不计数」之间自由切换,而模拟器代码不需要任何修改。更重要的是,使用 CountingDuckFactory 时,任何通过工厂创建的鸭子都自动带有计数装饰——彻底消除了遗漏包装的风险。
三种模式的协作
到目前为止,我们已经用了三种模式:
- 适配器让不兼容的鹅融入鸭子体系
- 装饰器在不修改鸭子类的前提下添加计数功能
- 抽象工厂把创建和装饰逻辑集中管理,确保不遗漏
每种模式的引入都是由前一步遗留的问题驱动的,而非提前规划好的。这正是复合模式的精髓——模式之间自然衔接。
第四步:组合模式——鸭群管理
问题
随着鸭子种类不断增加,在模拟器中逐一管理每只鸭子变得越来越繁琐。呱呱叫学家还提出了新需求:除了管理所有鸭子,还需要追踪特定的鸭子族群。我们需要一种方式来统一管理鸭群集合(包括子集合),并且能够批量执行操作。
这不就是组合模式的典型场景?统一处理「个体」和「整体」。
解决方案
创建一个 Flock(鸭群)类,它自身也实现 Quackable 接口——这样单只鸭子和一群鸭子可以被同等对待:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class Flock implements Quackable { List<Quackable> quackers = new ArrayList<>(); public void add(Quackable quacker) { quackers.add(quacker); } public void quack() { // 迭代调用每个成员的 quack() for (Quackable quacker : quackers) { quacker.quack(); } } } |
Flock 既是容器(持有一组 Quackable),又是 Quackable(自己也能 quack())——调用鸭群的 quack() 会递归地让每只鸭子都叫一声。而且鸭群里还可以嵌套鸭群,实现层次化管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | void simulate(AbstractDuckFactory duckFactory) { // 创建鸭子 Quackable mallardDuck = duckFactory.createMallardDuck(); Quackable redheadDuck = duckFactory.createRedheadDuck(); Quackable duckCall = duckFactory.createDuckCall(); Quackable rubberDuck = duckFactory.createRubberDuck(); Quackable gooseDuck = new GooseAdapter(new Goose()); // 创建主鸭群,包含所有鸭子 Flock flockOfDucks = new Flock(); flockOfDucks.add(mallardDuck); flockOfDucks.add(redheadDuck); flockOfDucks.add(duckCall); flockOfDucks.add(rubberDuck); flockOfDucks.add(gooseDuck); // 创建绿头鸭子鸭群——追踪特定族群 Flock flockOfMallards = new Flock(); flockOfMallards.add(duckFactory.createMallardDuck()); flockOfMallards.add(duckFactory.createMallardDuck()); flockOfMallards.add(duckFactory.createMallardDuck()); // 把绿头鸭群加入主鸭群 flockOfDucks.add(flockOfMallards); // 让整个主鸭群叫一声——所有鸭子(包括绿头鸭群)都会叫 simulate(flockOfDucks); } |
安全性 vs 透明性
组合模式在设计时有一个经典的取舍:叶节点(单个鸭子)要不要也拥有 add() 方法?
| 设计取向 | 做法 | 优势 | 代价 | 此处选择 |
|---|---|---|---|---|
| 透明组合 | 叶节点和组合节点有完全相同的方法集(包括 add()) |
客户端无需区分叶节点和组合节点,代码统一 | 叶节点的 add() 没有实际意义,可能被误调用 |
|
| 安全组合 | 只有组合节点(Flock)有 add() 方法,叶节点没有 |
不可能对叶节点执行无意义操作 | 客户端必须知道是 Flock 才能添加成员 |
✓ |
在之前学习组合模式的菜单系统例子中,叶节点和组合节点拥有完全相同的方法集——你甚至可以对菜单项调用 add()(虽然没有实际意义),那是「透明组合」的设计。
而在鸭子模拟器中,我们选择了安全组合:只有 Flock 拥有 add() 方法,普通鸭子没有。这意味着客户端必须知道某个 Quackable 是 Flock 才能向它添加成员——透明性降低了,但安全性提升了:你不可能意外地向一只橡皮鸭「添加子鸭子」。
选择哪种取向?
没有标准答案。透明组合让客户端代码更简洁统一,安全组合避免了无意义的操作。在不同场景下根据需求权衡即可——这里我们选择安全组合,是因为「给单只鸭子添加成员」这个操作在语义上完全没有意义。
第五步:观察者模式——追踪个体
问题
组合模式让我们能批量管理鸭群,但呱呱叫学家又提出了新需求:除了统计总叫声次数,还需要实时追踪单只鸭子的叫声——每当某只鸭子叫了一声,学家就想收到通知。
在不修改现有鸭子类的核心逻辑的前提下,如何实现这种「一叫就通知」的机制?
解决方案
这正是观察者模式的用武之地。我们需要让每一个 Quackable 对象都具备「被观察」的能力。
定义可观察接口
首先,定义一个 QuackObservable 接口,让所有 Quackable 都支持注册和通知观察者:
1 2 3 4 | public interface QuackObservable { void registerObserver(Observer observer); void notifyObservers(); } |
然后让 Quackable 继承这个接口——这意味着所有实现 Quackable 的类都必须支持观察者功能:
1 2 3 | public interface Quackable extends QuackObservable { void quack(); } |
封装观察者逻辑
如果让每个鸭子类都重复实现 registerObserver() 和 notifyObservers() 的逻辑,代码会大量重复。更优雅的做法是将核心的注册和通知机制封装到一个专门的 Observable 辅助类中,各个鸭子类通过组合持有它的引用,将观察者相关的方法调用委托给它处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Observable implements QuackObservable { List<Observer> observers = new ArrayList<>(); QuackObservable duck; // 持有被观察者的引用,用于在通知时告诉观察者「是谁叫的」 public Observable(QuackObservable duck) { this.duck = duck; // 创建时传入实际的鸭子对象 } public void registerObserver(Observer observer) { observers.add(observer); } public void notifyObservers() { for (Observer observer : observers) { observer.update(duck); // 通知时传入实际的鸭子对象 } } } |
为什么用辅助类而不是继承?
如果用一个抽象基类来实现观察者逻辑,那么所有鸭子都必须继承它——但 Java 是单继承的,这会严重限制类的设计。通过组合的方式,每个鸭子类内部持有一个 Observable 对象,只需编写最小化的委托代码即可获得完整的观察者能力。
这也体现了我们在第二节学过的设计原则:多用组合,少用继承。
将观察者整合到鸭子类中
每个 Quackable 实现类只需做两件事:
- 在构造器中创建
Observable辅助对象 - 将
registerObserver()和notifyObservers()委托给辅助对象
以 MallardDuck 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | public class MallardDuck implements Quackable { Observable observable; public MallardDuck() { observable = new Observable(this); // 把自己传给辅助类 } public void quack() { System.out.println("Quack"); notifyObservers(); // 叫完之后通知观察者 } public void registerObserver(Observer observer) { observable.registerObserver(observer); // 委托 } public void notifyObservers() { observable.notifyObservers(); // 委托 } } |
RedheadDuck、DuckCall、RubberDuck 的结构完全相同——只有 quack() 中打印的声音不同。
装饰器和适配器的适配
QuackCounter(装饰器)和 GooseAdapter(适配器)也需要适配观察者功能。它们的做法是将观察者相关的调用透传给被包装的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // QuackCounter 装饰器 public class QuackCounter implements Quackable { Quackable duck; static int numberOfQuacks; public QuackCounter(Quackable duck) { this.duck = duck; } public void quack() { duck.quack(); // 委托给被装饰对象(会触发 notifyObservers) numberOfQuacks++; } // 观察者注册透传给被装饰对象 public void registerObserver(Observer observer) { duck.registerObserver(observer); } public void notifyObservers() { duck.notifyObservers(); } } |
装饰器不需要自己管理观察者列表,它把注册请求直接转发给底层的鸭子对象。这样观察者收到通知时,能拿到的是真实的鸭子而非装饰器。
Flock 的观察者处理
Flock(鸭群)的观察者注册也很巧妙——它不维护自己的观察者列表,而是将注册请求递归分发给每一个成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class Flock implements Quackable { List<Quackable> quackers = new ArrayList<>(); public void add(Quackable quacker) { quackers.add(quacker); } public void quack() { for (Quackable quacker : quackers) { quacker.quack(); } } // 向鸭群注册 = 向每只鸭子注册 public void registerObserver(Observer observer) { for (Quackable quacker : quackers) { quacker.registerObserver(observer); } } public void notifyObservers() { // 由各个成员自行通知,Flock 不需要做额外的事 } } |
这意味着:向一个鸭群注册观察者,等价于向鸭群里的每一只鸭子都注册了这个观察者。之后不管哪只鸭子叫了,观察者都会收到通知。
观察者实现
最后,创建一个「呱呱叫学家」观察者:
1 2 3 4 5 6 7 8 9 | public interface Observer { void update(QuackObservable duck); } public class Quackologist implements Observer { public void update(QuackObservable duck) { System.out.println("Quackologist: " + duck + " just quacked!"); } } |
最终的鸭子模拟器
五种模式全部就位,来看看最终版本的模拟器如何将它们串联在一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | public class DuckSimulator { public static void main(String[] args) { DuckSimulator simulator = new DuckSimulator(); AbstractDuckFactory duckFactory = new CountingDuckFactory(); simulator.simulate(duckFactory); } void simulate(AbstractDuckFactory duckFactory) { // 1. 抽象工厂创建鸭子(自动包裹 QuackCounter 装饰器) Quackable mallardDuck = duckFactory.createMallardDuck(); Quackable redheadDuck = duckFactory.createRedheadDuck(); Quackable duckCall = duckFactory.createDuckCall(); Quackable rubberDuck = duckFactory.createRubberDuck(); // 2. 适配器让鹅融入鸭群 Quackable gooseDuck = new GooseAdapter(new Goose()); // 3. 组合模式管理鸭群 Flock flockOfDucks = new Flock(); flockOfDucks.add(mallardDuck); flockOfDucks.add(redheadDuck); flockOfDucks.add(duckCall); flockOfDucks.add(rubberDuck); flockOfDucks.add(gooseDuck); // 创建绿头鸭子族群 Flock flockOfMallards = new Flock(); flockOfMallards.add(duckFactory.createMallardDuck()); flockOfMallards.add(duckFactory.createMallardDuck()); flockOfMallards.add(duckFactory.createMallardDuck()); flockOfDucks.add(flockOfMallards); // 4. 观察者模式追踪叫声 Quackologist quackologist = new Quackologist(); flockOfDucks.registerObserver(quackologist); // 5. 启动模拟——一行代码驱动整个系统 System.out.println("Duck Simulator: With Composite - Flocks"); simulate(flockOfDucks); System.out.println("The ducks quacked " + QuackCounter.getQuacks() + " times"); } void simulate(Quackable duck) { duck.quack(); } } |
注意代码中的注释标号 1-5,每一步对应一种模式的应用。仅仅三十多行代码,就融合了五种设计模式——而每一种都在解决一个具体的问题。
全景回顾:五种模式的协作
让我们退后一步,俯瞰整个系统中模式的分工:
flowchart TD
subgraph 创建层["创建层"]
AF["AbstractDuckFactory\n(抽象工厂)"]
CF["CountingDuckFactory"]
AF --> CF
end
subgraph 结构层["结构层"]
QC["QuackCounter\n(装饰器)"]
GA["GooseAdapter\n(适配器)"]
FK["Flock\n(组合)"]
end
subgraph 行为层["行为层"]
OB["Observable\n(观察者辅助类)"]
QL["Quackologist\n(观察者)"]
end
subgraph 核心["核心接口"]
QI["Quackable"]
end
CF -->|"生产并包装"| QC
QC -->|"包裹"| QI
GA -->|"适配"| QI
FK -->|"聚合"| QI
QI -->|"被观察"| OB
OB -->|"通知"| QL
classDef create fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
classDef structure fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px
classDef behavior fill:#fff3e0,stroke:#ef6c00,stroke-width:2px
classDef core fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
class AF,CF create
class QC,GA,FK structure
class OB,QL behavior
class QI core
| 模式 | 角色 | 解决的问题 |
|---|---|---|
| 适配器 | GooseAdapter |
让接口不兼容的鹅融入鸭子体系 |
| 装饰器 | QuackCounter |
在不修改鸭子类的前提下添加叫声计数 |
| 抽象工厂 | CountingDuckFactory |
集中管理创建逻辑,确保所有鸭子都被正确装饰 |
| 组合 | Flock |
统一管理单只鸭子和鸭群,支持层次化结构 |
| 观察者 | Observable + Quackologist |
实时追踪个体鸭子的叫声,解耦通知机制 |
这五种模式各自独立,却在同一个系统中协同工作:工厂创建并装饰鸭子,适配器引入外来物种,组合统一管理群体,观察者追踪个体行为。它们之间没有冲突,反而相互增强——这就是复合模式的力量。
理解了模式如何协作之后,更值得思考的是它们为何能协作——背后有哪些可以复用的设计原则。
复合模式的设计启示
模式的自然涌现
鸭子模拟器最值得学习的一点是:五种模式不是一开始就规划好的,而是随着需求的演进逐步引入的。
- 鹅闯入 → 适配器
- 要计数 → 装饰器
- 怕遗漏 → 工厂
- 要分群 → 组合
- 要追踪 → 观察者
每一步都是在解决当前的具体问题,而不是为了「用上某个模式」而用。好的设计不是预先堆砌模式,而是让模式在需求驱动下自然涌现。
MVC:经典的复合模式
日常开发中最常见的复合模式实例当属 MVC(Model-View-Controller)。它本身就是三种设计模式的组合:
| MVC 组件 | 使用的模式 | 作用 |
|---|---|---|
| Model ↔ View | 观察者模式 | Model 状态变化时通知 View 更新,两者保持松耦合 |
| View ↔ Controller | 策略模式 | Controller 充当 View 的「策略」,可以替换不同的控制器实现 |
| View 内部 | 组合模式 | 界面由嵌套的组件(面板、按钮、文本框)组成树形结构 |
MVC 之所以成为最广泛使用的架构模式之一,正是因为它将三种模式有机地融合在一起,各自解决一个维度的问题,共同构建出一个灵活、可维护的系统架构。
何时使用复合模式
复合模式不是目标
不要为了「使用复合模式」而强行将多个模式塞进系统。复合模式是结果而非目标——它是多个设计问题同时存在时,各自对应的模式自然组合的产物。
正确的做法是:针对每个具体问题选择合适的模式,然后检查这些模式之间是否能协同工作。如果它们自然配合,那就是一个复合模式;如果需要强行拼凑,那说明设计可能出了问题。
思考题
回顾你学过的所有设计模式,还能想到哪些实际场景中会出现多种模式协作的情况?
比如:一个电商系统的订单处理流程中,可能同时用到哪些模式?