10.5.3 结构型设计模式
结构型模式关注类或对象如何组合成更大的结构。它们解决的不是“对象怎么创建”,也不是“算法怎么切换”,而是接口、层次、组合、共享和访问控制的问题。
结构型共有 7 种:适配器、桥接、组合、装饰、外观、享元、代理。
总览表
| 中文名 | 英文名 | 一句话意图 | 题干关键词 |
|---|---|---|---|
| 适配器 | Adapter | 将一个接口转换成客户希望的另一个接口 | 接口不兼容、转换接口、适配 |
| 桥接 | Bridge | 将抽象部分与实现部分分离,使二者独立变化 | 两个变化维度、抽象与实现分离 |
| 组合 | Composite | 用树形结构表示整体-部分,并统一使用方式 | 树形结构、整体-部分、一致使用 |
| 装饰 | Decorator | 动态给对象添加额外职责 | 动态添加职责、可叠加增强 |
| 外观 | Facade | 为复杂子系统提供统一高层接口 | 统一入口、简化调用、封装子系统 |
| 享元 | Flyweight | 共享大量细粒度对象,减少资源开销 | 共享、细粒度、大量对象 |
| 代理 | Proxy | 为对象提供替身,以控制对对象的访问 | 控制访问、远程代理、虚拟代理、保护代理 |
适配器:接口转换
适配器解决接口不兼容问题。课程用电源适配器举例:家用电源与设备所需电压不匹配,中间加适配器才能协同工作。翻译器也可以理解为适配器:双方语言不兼容,翻译负责转换。
classDiagram
class Target {
+request()
}
class Adapter {
+request()
}
class Adaptee {
+specificRequest()
}
Target <|.. Adapter
Adapter --> Adaptee适配器不是改变原对象内部实现,而是包一层转换接口,让客户按期望接口使用。
桥接:两个维度独立变化
桥接模式把抽象部分和实现部分分离,使它们可以独立变化。它的本质是把一个容易爆炸的多维继承结构拆成两个继承层次,再通过组合连接起来。
例如 Web 应用框架中,一个维度是应用类型:博客、新闻网站、网上商店;另一个维度是主题样式:深色、浅色。如果用继承硬编码,会产生“深色博客、浅色博客、深色商店、浅色商店……”的组合爆炸。桥接让应用类型和主题各自变化,运行时再组合。
classDiagram
class WebApp {
-Theme theme
+render()
}
class Blog
class Shop
class Theme {
+draw()
}
class DarkTheme
class LightTheme
WebApp <|-- Blog
WebApp <|-- Shop
Theme <|.. DarkTheme
Theme <|.. LightTheme
WebApp --> Theme : bridge桥接常被考成场景题。看到“两个独立变化维度”“不同应用 + 不同主题”“抽象与实现分离”,优先想到桥接。
组合:树形整体-部分
组合模式用树形结构表示整体-部分关系,使客户对单个对象和组合对象具有一致的使用方式。
典型例子是目录结构:文件和文件夹都可以被打开、移动、删除。文件是叶子,文件夹是组合节点,但使用者可以按统一接口操作。
classDiagram
class Component {
+operation()
}
class Leaf
class Composite {
+add(component)
+remove(component)
+operation()
}
Component <|.. Leaf
Component <|.. Composite
Composite o-- Component组合模式的关键不是“组合”这个日常词,而是树形结构、整体-部分、一致对待。
装饰:动态添加职责
装饰模式在不改变原对象的前提下,动态附加额外职责。课程用饮品加配料解释:不必为所有配料组合都派生子类,而是把珍珠、椰果、布丁等作为装饰对象,按需要叠加。
classDiagram
class Component {
+operation()
}
class ConcreteComponent
class Decorator {
-Component component
+operation()
}
class ConcreteDecoratorA
class ConcreteDecoratorB
Component <|.. ConcreteComponent
Component <|.. Decorator
Decorator <|-- ConcreteDecoratorA
Decorator <|-- ConcreteDecoratorB
Decorator --> Component装饰比继承更灵活,因为职责可以运行时组合。它解决的是“功能组合爆炸”的问题。
外观:统一入口
外观模式定义一个高层接口,把复杂子系统封装起来,对外提供一致入口。客户不需要知道内部七八个子系统如何协作,只需要调用外观接口。
flowchart LR
Client["客户程序"] --> Facade["Facade统一入口"]
Facade --> A["子系统A"]
Facade --> B["子系统B"]
Facade --> C["子系统C"]外观和适配器都可能出现“接口”二字,但含义不同:
| 对比项 | 适配器 | 外观 |
|---|---|---|
| 解决问题 | 原接口不兼容 | 子系统太复杂 |
| 主要动作 | 转换接口 | 简化调用入口 |
| 是否重点改造子系统 | 不改原接口,做转换 | 不要求修改子系统,做统一封装 |
享元:共享细粒度对象
享元模式支持大量细粒度对象的共享,以减少对象数量和资源开销。课程用文字举例:如果英文字符可以共享 26 个字母对象,再用外部状态组合出文章,就能避免为每个字符位置都创建全新对象。
适合场景:
- 系统中存在大量相似小对象。
- 对象内部状态可以共享。
- 外部状态可由上下文提供。
- 创建大量对象会造成明显资源压力。
享元被新方案替代的常见原因是:现代运行时和内存管理比过去更强,很多场景不再值得为极小对象共享增加复杂度。但在字体、图形、游戏对象池、缓存等高频对象场景,它仍有价值。
代理:控制访问
代理模式为真实对象提供一个替身,由代理控制对真实对象的访问。代理与真实对象通常实现同一接口,客户通过代理访问目标。
classDiagram
class Subject {
+request()
}
class RealSubject
class Proxy {
-RealSubject realSubject
+request()
}
Subject <|.. RealSubject
Subject <|.. Proxy
Proxy --> RealSubject常见代理:
| 类型 | 用途 |
|---|---|
| 远程代理 | 代表不同地址空间中的对象 |
| 虚拟代理 | 延迟创建昂贵对象,或缓存目标信息 |
| 保护代理 | 检查权限,控制访问 |
代理和中介者容易混淆:代理只是控制对某个对象的访问;中介者会改变多个对象之间的交互方式。
结构型模式易混辨析
| 易混组 | 区分关键 |
|---|---|
| 适配器 vs 外观 | 适配器转换不兼容接口;外观封装复杂子系统,提供统一入口 |
| 装饰 vs 代理 | 装饰给对象增加职责;代理控制访问 |
| 组合 vs 装饰 | 组合是树形整体-部分;装饰是运行时叠加功能 |
| 桥接 vs 访问者 | 桥接把两个变化维度组合起来;访问者选择一种访问方式遍历对象结构 |
例题
自查要点
- 桥接模式为什么能避免多维继承爆炸?
- 组合模式中的“整体”和“部分”为什么要统一接口?
- 装饰和代理都包了一层对象,它们的目的有什么不同?
- 外观和适配器都涉及接口,为什么不是同一种问题?