一到周五,小王的心情就会好一点。倒不是工作少了,而是想到这一天忙完,后面接着双休,心里就有盼头。
地铁上,他抬头看标识,发现一件平时没留意的事:5 号线、10 号线常见的是"北京地铁",4 号线却写着"京港地铁"。
再想想,北京的机场也各有各的名字:首都机场、大兴机场。可在成都坐地铁,日常看到的统一称谓就是"成都地铁"。
都是出行,名称和背后的主体并不完全一样。对乘客来说,常做的事还是那几样:查线路、进站、上车、到站下车。坐几号线是一条信息,谁来运营是另一条信息,不必揉成一团。

同样是在北京坐地铁,线路和运营方并不是同一个概念
今天聊的桥接模式(Bridge),处理的就是这类问题:一个概念有两条独立变化的维度,别用继承把所有组合写死,而是用组合把它们接起来。系列第十三篇。
01 先拆清楚:是"线路"在变,还是"运营方"在变
先把场景压成最简单的模型:普通地铁线路由运营方调度,乘客调用 ride(),就能知道这条线怎么运行。
如果把线路类型和运营实现直接绑在一起,可能是这样:
java
class MtrRegularLine {
void ride() {
System.out.println("京港地铁调度,各站停靠");
}
}
class BeijingSubwayRegularLine {
void ride() {
System.out.println("北京地铁调度,各站停靠");
}
}
线路类型和运营实现,一起绑进了具体类
只有两个类,当然能跑,也没什么难懂。
可如果抽象侧还要增加"大站快车",实现侧又要支持另一种运营系统,继承组合就会开始膨胀:MtrRegularLine、MtrExpressLine、BeijingSubwayRegularLine、BeijingSubwayExpressLine......每添一边,另一边都可能跟着复制一批。
这里的"运营调度"只是为了演示结构做的简化,不是现实地铁系统的真实接口。真正要看的是:线路类型和运营实现,是两条可以各自扩展的维度。
02 先把运营实现抽出去
给运营方留一个独立接口:
java
interface MetroOperator {
void dispatch(String lineName);
}
class BeijingSubwayOperator implements MetroOperator {
public void dispatch(String lineName) {
System.out.println("北京地铁调度 " + lineName);
}
}
class BeijingMtrOperator implements MetroOperator {
public void dispatch(String lineName) {
System.out.println("京港地铁调度 " + lineName);
}
}
运营方只管怎么调度,不必知道自己被装进哪条线路
这一层叫实现(Implementation)。它不是单指"接口的实现类",而是桥接模式里相对独立的一整套实现体系。
北京地铁和京港地铁只是这里的两个示例。以后再换一种运营实现,新增一个 MetroOperator 实现类即可,不需要回头给普通线路、大站快车各复制一份。
03 线路拿着运营方,这座桥就搭起来了
另一边,线路只保留一个 MetroOperator 引用。至于当前是北京地铁还是京港地铁,由组装时传进来的对象决定。
java
abstract class SubwayLine {
protected final String name;
protected final MetroOperator operator;
SubwayLine(String name, MetroOperator operator) {
this.name = name;
this.operator = operator;
}
abstract void ride();
}
class RegularLine extends SubwayLine {
RegularLine(String name, MetroOperator operator) {
super(name, operator);
}
void ride() {
operator.dispatch(name);
System.out.println("普通线路:各站停靠");
}
}
class ExpressLine extends SubwayLine {
ExpressLine(String name, MetroOperator operator) {
super(name, operator);
}
void ride() {
operator.dispatch(name);
System.out.println("大站快车:只停部分站点");
}
}
线路负责自己的乘车语义,具体调度交给运营实现
SubwayLine 这一侧叫抽象(Abstraction) ,RegularLine 和 ExpressLine 是扩充后的抽象。它们通过 operator 这个对象引用,把该由运营方完成的工作委托出去。
这座"桥"不是一段网络连接,也不是一个名叫 Bridge 的类,就是抽象一侧持有的实现对象引用,以及随之而来的委托关系。

两边各自扩展,中间用组合和委托连起来
组装以后,这三条线路可以这样表示:
java
MetroOperator beijingSubway = new BeijingSubwayOperator();
MetroOperator beijingMtr = new BeijingMtrOperator();
SubwayLine line4 = new RegularLine("4 号线", beijingMtr);
SubwayLine line5 = new RegularLine("5 号线", beijingSubway);
SubwayLine line10 = new RegularLine("10 号线", beijingSubway);
line4.ride();
line5.ride();
line10.ride();
线路知道"我是谁",运营方知道"怎么调度"
运行结果是:
text
京港地铁调度 4 号线
普通线路:各站停靠
北京地铁调度 5 号线
普通线路:各站停靠
北京地铁调度 10 号线
普通线路:各站停靠
三条线路分别委托自己的运营方,再执行各自的线路行为
新增 ExpressLine 时,两个已有运营方都能直接使用;新增运营实现时,也不必修改 RegularLine 和 ExpressLine。注意,是新增扩展时不必复制整套组合,不是任何改动都永远不用碰已有代码。接口本身一旦变化,两侧该调整还是得调整。
04 名字不同,不代表一定要架桥
这里有两处类比失真,还有一个使用边界,得挑明。
第一,现实中的线路与运营方,不可能像换接口实现那样随意插拔。车辆、信号、调度、人员和管理规范都深度关联,更换运营主体更不是改一行组装代码。地铁类比只负责说明"两条维度通过引用协作",不负责描述真实运营交接。
第二,标识不同,不等于内部实现一定不同;标识统一,也不等于背后只有一套系统。"成都地铁"是统一呈现在乘客面前的品牌称谓,不能据此推断它的代码、设备或内部协作都只有一种。首都机场和大兴机场的名字,也不能直接拿来证明谁是桥接模式------机场名称和地铁运营标识本来就不是同一个层面的信息。
第三,如果程序里只是记录线路名和运营方名称,两个字段就够了:
java
record LineInfo(String name, String operatorName) {}
只保存名称时,这样最清楚,不需要桥接
桥接解决的是两条行为体系需要独立扩展,不是给所有"两个名词"都配两套继承层次。名字不同是观察的起点,不是使用模式的证据。
05 跟策略、适配器,分别差在哪儿
策略模式(Day 4)管"选一个做法完成目标":早餐可以自己做,也可以楼下买。桥接管"抽象和实现两条体系各自扩展":普通线路、大站快车可以继续发展,运营实现也可以单独增加。
单看一段代码,两者都可能是"一个对象持有接口引用,再委托调用"。区别主要不在这一行语法,而在设计意图和对象关系:策略通常围绕某个可替换算法,桥接则刻意保留两侧各自的层次。别指望靠一句"用了组合"把两者分开。
适配器模式管"把已有但不兼容的接口接起来" :目标接口要 ride(),旧类只有 takeTrain(),中间加一层转换。桥接更强调把两条变化维度主动拆开,让它们在统一约定下协作。
所以,北京地铁和京港地铁名称不同,并不需要适配器把 4 号线"转换"成 5 号线。真要出现已有调度系统接口对不上新线路接口,才轮到适配器登场。
Java 里常用 JDBC 来说明桥接思想:应用面对统一的 JDBC API,不同数据库由各自 Driver 提供实现,两侧可以分别发展。不过 JDBC 还包含工厂等其他设计,不适合被贴成"只有桥接"的标本。
06 什么时候,别急着拆成两边
**只有名称或配置不同,用字段。**像 LineInfo 那样能表达清楚,就别为"4 号线""5 号线"各建一类。
**只有一边真正变化,先用简单接口。**如果线路类型固定,只是调度算法可替换,策略或普通依赖注入往往更直白,不必硬造两套抽象。
**两边始终一一对应,也别拆。**一种线路只有一种实现,而且没有独立扩展需求,桥接只会增加跳转。
判断标准可以落在一句话上:**这里是否确实有两条独立变化的维度,而把它们组合起来后,能避免成批复制"抽象 × 实现"的类?**是,桥接才有用武之地。
07 各走各的,也能一起把事办成
地铁继续往前开。小王只是觉得这些叫法挺有意思,至于换乘,还是看线路、看方向、看站名。
乘客不必先研究运营公司才能坐车,调用方也不必把所有实现细节塞进一个类。该拆开的拆开,该协作的协作。
跟前面十二篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把请求做成对象,代理管谁能碰到真东西,观察者管变化后通知谁,外观管一堆零件只留一个入口,状态管当前状态下怎么做,责任链管请求由哪一环接住,原型管拿哪个现成对象复制,桥接管抽象和实现怎么独立扩展、协同工作。
带走一句话:桥接 = 用组合把抽象与实现分开,让两条变化维度独立扩展,再通过委托一起完成工作。
线路和运营方可以各走各的,乘客要去的,始终是下一站。
今天是周五。上完这一天,就好好过双休。列车往下一站开,周末也在前头等着了。
参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF 原著)------桥接模式的原始定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化
- Refactoring Guru · Bridge------桥接模式的结构、两条独立层次,以及从继承转向对象组合的做法
- Oracle Java API · Driver(Java 21 文档)------JDBC Driver 的统一接口,以及不同数据库驱动如何接入 JDBC
- 北京京港地铁 · 4号线---大兴线------文中 4 号线及京港地铁标识对应的官方线路信息
- 北京地铁官方网站 · 站点及周边信息------文中 5 号线、10 号线对应的北京地铁官方线路信息