# 10 桥接（结构型）

> 一个东西有两个独立变化的维度，用继承会让类的数量乘起来爆炸。桥接把两个维度拆成两条独立的类层次，用组合搭一座桥，让 M×N 变回 M+N。附 database/sql 就是一座桥的实锤。

- 作者：David（道雾轩）
- 专栏：经典设计模式浅析（https://daiw.org/manual/design-patterns.md）
- 最后更新：2026-09-13
- 原文：https://daiw.org/manual/design-patterns/bridge
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 10 桥接 · Bridge（结构型）

桥接是结构型里公认**最难理解**的一个，倒不是因为它复杂，而是因为 GoF 给它起的名字(“抽象部分”“实现部分”)极其抽象，劝退了一批人。别怕，我们用大白话把它拆开。

## 一、这是什么：遥控器 × 设备，两个能各自进化的维度

你有遥控器，也有被控的设备。现在注意，这里有**两个会独立变化的维度**：

- **遥控器**这一维会变：有基础遥控器，有带静音的高级遥控器，以后可能有带语音的……
- **设备**这一维也会变：能控电视，能控收音机，以后可能控空调、控音箱……

关键洞察：**这两个维度是正交的、各自独立进化的。** “高级遥控器”应该能控任何设备，“电视”应该能被任何遥控器控。它们不该被绑死。

桥接模式就是：**把这两个维度拆成两套独立的类层次，让“遥控器”持有一个“设备”(这就是那座“桥”)，两边各自随便扩展，谁也不拖累谁。**

## 二、解决什么痛：继承会让类的数量“乘起来”

假设你头铁，用继承来做。你会得到：

```
BasicRemoteForTV、BasicRemoteForRadio、
AdvancedRemoteForTV、AdvancedRemoteForRadio......
```

2 种遥控器 × 2 种设备 = **4 个类**。现在加一种设备(空调)，变成 2×3=6；再加一种遥控器，3×3=9……**类的数量是两个维度相乘的**。这就是继承的“笛卡尔积爆炸”：每个维度增长，总数是乘法级膨胀。

桥接把它从**乘法**变回**加法**：

| | 继承(维度耦合) | 桥接(维度解耦) |
| --- | --- | --- |
| 2 遥控 × 2 设备 | 2×2 = **4** 个类 | 2 + 2 = **4** 个类 |
| 3 遥控 × 4 设备 | 3×4 = **12** 个类 | 3 + 4 = **7** 个类 |
| M 遥控 × N 设备 | **M×N** | **M + N** |

维度一多，M+N 对 M×N 是碾压性的优势。

<Callout type="info">
  **桥接隔离的“会变的东西”，是“两个正交维度各自的变化”。** 它认定这两维互不相干，于是干脆拆成两条平行的继承线，中间用一个“持有”关系(组合)搭桥。任一维度加东西，都是给那条线**加一个类**(加法)，而不是给整个矩阵**填一格**(乘法)。
</Callout>

## 三、怎么实现：抽象持有实现，搭一座桥

先破除名词障碍。GoF 说的：

- **“抽象部分”(Abstraction)** = 遥控器这一维(高层控制逻辑，面向用户)。
- **“实现部分”(Implementor)** = 设备这一维(底层具体干活)。

注意这里的“实现”**不是**“接口的实现类”那个意思，而是“另一个维度里真正干活的那方”。这个词是所有误解的源头，记住它=设备维就行。

```go title="go/bridge/main.go（核心）"
// 实现维:设备(TV/Radio 各自实现)
type Device interface {
	Name() string; IsEnabled() bool; Enable(); Disable()
	GetVolume() int; SetVolume(percent int)
}

// 抽象维:遥控器,持有一个 Device —— 这个"持有"就是那座"桥"
type RemoteControl struct {
	device Device        // ← 桥:遥控器这一维,通过它连到设备那一维
}
func (r *RemoteControl) TogglePower() string {
	if r.device.IsEnabled() { r.device.Disable(); return r.device.Name()+" 已关闭" }
	r.device.Enable(); return r.device.Name()+" 已开启"
}

// 扩展抽象维:高级遥控器,组合复用基础遥控器,再加新功能
type AdvancedRemoteControl struct {
	RemoteControl        // 内嵌复用
}
func (r *AdvancedRemoteControl) Mute() string {
	r.device.SetVolume(0); return r.device.Name()+" 已静音"
}
```

于是**任意遥控器 × 任意设备**自由组合，加维度只需加一个类：

```go
basic := &RemoteControl{device: NewTV()}              // 基础遥控 控 电视
adv := NewAdvancedRemoteControl(NewRadio())           // 高级遥控 控 收音机
adv.Mute()                                            // 高级功能,对任何设备都好使
```

## 四、有哪些坑

- **最大的坑是“看不出该用它”。** 桥接的收益，建立在你**提前识别出“这里有两个独立维度”**之上。识别对了，它优雅无比；识别错了(其实只有一个维度，或者两维根本纠缠在一起)，硬套桥接就是给自己找麻烦。经验：当你发现类名开始出现 `AForB`、`XxxWithYyy` 这种“两个词拼起来”的组合命名时，警报响起——大概率该上桥接了。
- **和策略模式结构像，意图不同。** 两者都是“持有一个接口对象”。但策略是“**换算法**”(同一件事的不同做法，运行时替换)，桥接是“**解耦两个维度**”(两件正交的事，各自演化)。策略通常一个维度，桥接明确是两个。
- **和适配器分清：** 适配器是**事后补救**(两套接口没对上，加层翻译)，桥接是**事前规划**(设计之初就预见双维度，主动拆开)。一个救火，一个防火。
- **别过早桥接。** 只有一个维度会变的时候，引入桥接纯属自缚手脚。等第二个独立维度真的冒头，再重构成桥接不迟。

## 五、开源里怎么用

- **Go `database/sql` —— 一座教科书级的桥。** `sql.DB` / `sql.Rows` 这套是“抽象维”(统一的数据库操作 API，你的业务代码用它)，`database/sql/driver` 定义的接口由各家驱动实现，是“实现维”(MySQL 驱动、Postgres 驱动各自干活)。你的 SQL 代码(抽象维)和具体数据库(实现维)**独立变化**：换数据库只换驱动，业务代码不动；标准库升级 API，驱动也不用重写。这就是桥接。
- **JDBC**:Java 世界的同款——`java.sql` 是抽象维，各厂商的 `Driver` 是实现维，`DriverManager` 负责搭桥。GoF 书里桥接的原型场景之一。
- **日志门面 × 后端**：SLF4J(抽象维)× logback/log4j(实现维)；Go `log/slog` 的 `Logger`(抽象维)× `Handler`(实现维)。你写日志的代码和“日志往哪写、什么格式”两个维度彻底解耦。
- **跨平台绘图/GUI**：一个 `Shape`/`Window` 抽象维，配一套各平台的绘制“实现维”(Windows GDI、macOS Quartz、Linux X11)。图形逻辑和平台绘制各自演化——GoF 原书举的正是这个例子。

---

桥接管“两个维度”，下一篇[组合](https://daiw.org/manual/design-patterns/composite)管“树形结构”：当对象自己能包含同类对象、层层嵌套成一棵树时，怎么让“单个”和“一组”被一视同仁地对待。
