系列定位:软考软件设计师 / 系统架构设计师 — 结构型模式专题第 2 讲
考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现
难度等级:⭐⭐⭐⭐☆(理解抽象与实现分离的思想是关键,类图识别是高频考点)
一、考纲定位与模式定义
1.1 考纲要求
桥接模式在软考中属于结构型模式的中高难度内容。考察形式包括:
上午选择题:判断场景描述所属模式;识别桥接模式与适配器模式、装饰模式的区别;判断类图中是否存在两个独立变化的维度(抽象层与实现层)
下午设计题:补全 Abstraction 类中持有的 Implementor 引用;补全
operation()方法中将调用委托给 Implementor 的逻辑;识别类图中两个独立继承树的关系
1.2 模式定义
桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
核心意图:当一个类存在两个独立变化的维度(如"形状"和"绘制方式"、"消息类型"和"发送渠道"),如果采用多继承,类的数量会呈指数级增长(类爆炸)。桥接模式通过组合代替继承,将两个维度拆分为两个独立的继承树,再通过一个 "桥"(Abstraction 持有 Implementor 引用)连接起来,让两个维度可以独立扩展。
通俗理解:
假设你要设计一个图形系统,有圆形、矩形两种形状,有红色、蓝色两种颜色。如果用多继承,你需要:RedCircle、BlueCircle、RedRectangle、BlueRectangle—— 共 4 个类。如果新增一个绿色,需要再加 2 个类;新增一个三角形,需要再加 3 个类。这就是类爆炸。
桥接模式的思路是:把 "形状" 和 "颜色" 拆成两个独立的继承树。形状类里组合一个颜色对象。新增颜色只需扩展颜色树,新增形状只需扩展形状树,互不干扰。
二、UML 类图与角色划分
┌─────────────────────────────────────────────────────────┐ │ Client │ │ + main() │ │ ├── Abstraction abs = new RefinedAbstraction(impl); │ │ └── abs.operation(); │ └───────────────────────┬─────────────────────────────────┘ │ uses ▼ ┌─────────────────────────────────────────────────────────┐ │ Abstraction │ │ ── 抽象层 ── │ │ - impl: Implementor ◄── 桥接点:组合实现层 │ │ + operation() { │ │ impl.operationImpl(); // 委托给实现层 │ │ } │ └───────────────────────┬─────────────────────────────────┘ ┌────────────┴────────────┐ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ RefinedAbstractionA │ │ RefinedAbstractionB │ │ (如:Circle) │ │ (如:Rectangle) │ │ + operation() │ │ + operation() │ │ (可扩展抽象行为) │ │ (可扩展抽象行为) │ └─────────────────────┘ └─────────────────────┘ │ │ │ ┌─────────────────────┐ │ └──┤ <<interface>> ├──┘ │ Implementor │ │ + operationImpl() │ └──────────┬──────────┘ ┌───────────┴───────────┐ ▼ ▼ ┌───────────────────┐ ┌──────────────────┐ │ ConcreteImplA │ │ ConcreteImplB │ │ (如:RedColor) │ │ (如:BlueColor) │ │ + operationImpl() │ │ + operationImpl()│ └───────────────────┘ └──────────────────┘| 角色 | 职责 | 软考填空关键词 |
|---|---|---|
| Abstraction(抽象) | 定义抽象类的接口,组合Implementor 引用,将操作委托给实现层 | abstract class+protected Implementor impl |
| RefinedAbstraction(扩充抽象) | 继承 Abstraction,实现具体的抽象行为(如具体的形状) | extends Abstraction |
| Implementor(实现接口) | 定义实现类的接口,与 Abstraction 的接口可以不同(通常更简单) | interface+operationImpl() |
| ConcreteImplementor(具体实现) | 实现 Implementor 接口,完成具体的实现(如具体的颜色) | implements Implementor |
| Client(客户端) | 创建 ConcreteImplementor 并注入 Abstraction,调用抽象层接口 | new RefinedAbstraction(new ConcreteImpl()) |
类图识别要点:软考类图中,桥接模式的核心特征是存在两个独立的继承树(Abstraction 继承树 和 Implementor 继承树),且 Abstraction 与 Implementor 之间是组合/聚合关系(带空心菱形的实线)。如果类图里只有一个继承树 + 一个转换类 → 适配器;如果有两个独立继承树通过组合关联 → 桥接。
三、场景一:图形绘制系统(GoF 经典例子,形状与颜色分离)
业务背景:设计一个图形系统,支持圆形、矩形两种形状,支持红色、蓝色两种颜色绘制。每个形状需要被绘制出来,但绘制方式(颜色)与形状本身是两个独立变化的维度。使用桥接模式避免类爆炸。
说明:这是 GoF 原书中桥接模式的经典例子,也是软考下午题最爱考的 "两个独立维度" 场景。如果没有桥接模式,需要RedCircle、BlueCircle、RedRectangle、BlueRectangle共 4 个类;如果用桥接,只需要 2 个形状类 + 2 个颜色类 = 4 个类(数量相同但扩展性完全不同)。新增一个绿色,桥接模式只需加 1 个类;多继承则需要加 2 个类。
3.1 代码实现
// Implementor:绘制接口(实现层) public interface Color { void applyColor(); } // ConcreteImplementor:红色实现 public class RedColor implements Color { @Override public void applyColor() { System.out.println("涂上红色"); } } // ConcreteImplementor:蓝色实现 public class BlueColor implements Color { @Override public void applyColor() { System.out.println("涂上蓝色"); } } // Abstraction:形状抽象类(抽象层) public abstract class Shape { // 桥接点:组合实现层接口 protected Color color; // 通过构造器注入实现层对象(关键!) public Shape(Color color) { this.color = color; } // 抽象方法:绘制形状(由子类实现具体形状) public abstract void draw(); // 通用方法:涂色(委托给实现层) public void paint() { color.applyColor(); } } // RefinedAbstraction:圆形(扩充抽象) public class Circle extends Shape { public Circle(Color color) { super(color); } @Override public void draw() { System.out.print("绘制圆形,"); paint(); } } // RefinedAbstraction:矩形(扩充抽象) public class Rectangle extends Shape { public Rectangle(Color color) { super(color); } @Override public void draw() { System.out.print("绘制矩形,"); paint(); } } // Client:客户端 public class Client { public static void main(String[] args) { Shape redCircle = new Circle(new RedColor()); redCircle.draw(); Shape blueRectangle = new Rectangle(new BlueColor()); blueRectangle.draw(); Shape blueCircle = new Circle(new BlueColor()); blueCircle.draw(); } }关键体会:Shape(抽象层)与Color(实现层)通过组合关联。新增一种颜色(如GreenColor),只需新增 1 个实现类,所有形状自动支持绿色。新增一种形状(如Triangle),只需新增 1 个抽象类,自动支持所有颜色。两个维度独立扩展,互不干扰。
四、场景二:消息发送系统(消息类型与发送渠道分离)
业务背景:系统需要支持短信、邮件两种消息类型,支持即时发送、延迟发送两种发送渠道。消息内容(标题、正文)的组装逻辑由消息类型决定,发送逻辑由渠道决定。两个维度独立变化,使用桥接模式解耦。
说明:这是软考下午题中常见的 "业务逻辑 + 技术实现" 分离场景。消息类型是业务抽象层,发送渠道是技术实现层。
4.1 代码实现
// Implementor:发送渠道接口(实现层) public interface SendChannel { void doSend(String title, String content, String receiver); } // ConcreteImplementor:即时发送 public class InstantChannel implements SendChannel { @Override public void doSend(String title, String content, String receiver) { System.out.println("[即时发送] 收件人: " + receiver + ", 标题: " + title); } } // ConcreteImplementor:延迟发送(定时队列) public class DelayedChannel implements SendChannel { @Override public void doSend(String title, String content, String receiver) { System.out.println("[延迟发送] 已加入定时队列,收件人: " + receiver + ", 标题: " + title); } } // Abstraction:消息抽象类(抽象层) public abstract class Message { protected SendChannel channel; public Message(SendChannel channel) { this.channel = channel; } public abstract String assembleTitle(); public abstract String assembleContent(); public void send(String receiver) { String title = assembleTitle(); String content = assembleContent(); channel.doSend(title, content, receiver); } } // RefinedAbstraction:短信消息 public class SmsMessage extends Message { private String code; public SmsMessage(SendChannel channel, String code) { super(channel); this.code = code; } @Override public String assembleTitle() { return "验证码通知"; } @Override public String assembleContent() { return "您的验证码是: " + code + ",5分钟内有效。"; } } // RefinedAbstraction:邮件消息 public class EmailMessage extends Message { private String subject; private String body; public EmailMessage(SendChannel channel, String subject, String body) { super(channel); this.subject = subject; this.body = body; } @Override public String assembleTitle() { return subject; } @Override public String assembleContent() { return body; } } // Client public class Client { public static void main(String[] args) { Message sms = new SmsMessage(new InstantChannel(), "384921"); sms.send("13800138000"); Message email = new EmailMessage( new DelayedChannel(), "月度报表", "请查收附件中的月度销售报表。" ); email.send("zhangsan@company.com"); } }关键体会:消息类型(Sms/Email)和发送渠道(Instant/Delayed)是两个独立维度。新增一种消息类型(如PushMessage),只需扩展抽象层;新增一种渠道(如BatchChannel),只需扩展实现层。客户端可以自由组合,无需为每种组合创建新类。
五、场景三:跨平台 UI 渲染(组件与渲染引擎分离)
业务背景:设计一个跨平台 UI 框架,需要支持按钮、文本框两种组件,支持OpenGL、DirectX两种渲染引擎。组件决定 "画什么"(位置、大小、文字),渲染引擎决定 "怎么画"(底层 API 调用)。两个维度独立变化,且未来可能新增更多组件和更多渲染引擎。
说明:这是实际开发中桥接模式的典型应用,也是软考类图识别题的常见场景。抽象层是 UI 组件,实现层是渲染引擎。
5.1 代码实现
// Implementor:渲染引擎接口(实现层) public interface RenderEngine { void renderButton(String text, int x, int y, int w, int h); void renderTextField(String text, int x, int y, int w, int h); } // ConcreteImplementor:OpenGL 渲染 public class OpenGLEngine implements RenderEngine { @Override public void renderButton(String text, int x, int y, int w, int h) { System.out.println("[OpenGL] 绘制按钮 '" + text + "' 在 (" + x + "," + y + ") 大小 " + w + "x" + h); } @Override public void renderTextField(String text, int x, int y, int w, int h) { System.out.println("[OpenGL] 绘制文本框 '" + text + "' 在 (" + x + "," + y + ") 大小 " + w + "x" + h); } } // ConcreteImplementor:DirectX 渲染 public class DirectXEngine implements RenderEngine { @Override public void renderButton(String text, int x, int y, int w, int h) { System.out.println("[DirectX] 绘制按钮 '" + text + "' 在 (" + x + "," + y + ") 大小 " + w + "x" + h); } @Override public void renderTextField(String text, int x, int y, int w, int h) { System.out.println("[DirectX] 绘制文本框 '" + text + "' 在 (" + x + "," + y + ") 大小 " + w + "x" + h); } } // Abstraction:UI 组件抽象类(抽象层) public abstract class UIComponent { protected RenderEngine engine; protected int x, y, width, height; public UIComponent(RenderEngine engine, int x, int y, int w, int h) { this.engine = engine; this.x = x; this.y = y; this.width = w; this.height = h; } public abstract void draw(); } // RefinedAbstraction:按钮组件 public class Button extends UIComponent { private String label; public Button(RenderEngine engine, int x, int y, int w, int h, String label) { super(engine, x, y, w, h); this.label = label; } @Override public void draw() { engine.renderButton(label, x, y, width, height); } } // RefinedAbstraction:文本框组件 public class TextField extends UIComponent { private String text; public TextField(RenderEngine engine, int x, int y, int w, int h, String text) { super(engine, x, y, w, h); this.text = text; } @Override public void draw() { engine.renderTextField(text, x, y, width, height); } } // Client public class Client { public static void main(String[] args) { RenderEngine gl = new OpenGLEngine(); UIComponent btn1 = new Button(gl, 10, 10, 100, 30, "确定"); UIComponent tf1 = new TextField(gl, 10, 50, 200, 25, "请输入用户名"); btn1.draw(); tf1.draw(); System.out.println("--- 切换渲染引擎 ---"); RenderEngine dx = new DirectXEngine(); UIComponent btn2 = new Button(dx, 10, 10, 100, 30, "确定"); UIComponent tf2 = new TextField(dx, 10, 50, 200, 25, "请输入用户名"); btn2.draw(); tf2.draw(); } }六、三种场景对比与演进思路
| 维度 | 场景一:图形绘制 | 场景二:消息发送 | 场景三:跨平台 UI |
|---|---|---|---|
| 抽象层(Abstraction) | Shape(形状) | Message(消息) | UIComponent(组件) |
| 实现层(Implementor) | Color(颜色) | SendChannel(渠道) | RenderEngine(引擎) |
| 抽象层扩展 | Circle、Rectangle | SmsMessage、EmailMessage | Button、TextField |
| 实现层扩展 | RedColor、BlueColor | InstantChannel、DelayedChannel | OpenGLEngine、DirectXEngine |
| 桥接点 | Shape组合Color | Message组合SendChannel | UIComponent组合RenderEngine |
| 委托逻辑 | color.applyColor() | channel.doSend() | engine.renderXxx() |
| 避免的问题 | 形状×颜色的类爆炸 | 消息类型×渠道的类爆炸 | 组件×引擎的类爆炸 |
| 软考考法 | 下午大题(补全抽象层委托逻辑) | 下午大题(补全构造器注入) | 类图识别(两个继承树) |
七、桥接模式 vs 适配器模式:核心对比
| 对比项 | 桥接模式 | 适配器模式 |
|---|---|---|
| 设计时机 | 事前设计:在系统设计阶段就将抽象与实现分离 | 事后补救:已有代码接口不兼容,需要转换 |
| 目的 | 让抽象和实现独立变化,避免类爆炸 | 让不兼容的接口能够协同工作 |
| 类图结构 | 两个独立的继承树(抽象层 + 实现层) | 一个 Adapter 类连接 Target 和 Adaptee |
| 是否改变接口 | ❌ 不改变(抽象层和实现层各自定义自己的接口) | ✅ 改变(Adapter 将 Adaptee 接口转为 Target 接口) |
| 关系类型 | 组合/聚合(Abstraction 持有 Implementor) | 组合/继承(Adapter 持有/继承 Adaptee) |
| 类数量 | 减少(避免多继承的类爆炸) | 增加(新增 Adapter 类) |
| 软考判断 | 看到 "两个独立维度"、"抽象与实现分离" → 桥接 | 看到 "接口转换"、"不兼容" → 适配器 |
记忆口诀:桥接是 "事前拆桥(抽象与实现分离)",适配器是 "事后搭桥(接口转换兼容)"。
八、软考高频考点与易混淆辨析
8.1 高频考点
| 考点 | 内容 |
|---|---|
| 模式分类 | 结构型模式(GoF 23 正式成员) |
| 核心四角色 | Abstraction(抽象)、RefinedAbstraction(扩充抽象)、Implementor(实现接口)、ConcreteImplementor(具体实现) |
| 核心思想 | 组合代替继承:将两个独立变化的维度拆分为两个继承树,通过组合关联 |
| 桥接点 | Abstraction 中声明protected Implementor impl字段,通过构造器注入 |
| 方法委托 | Abstraction 的operation()内部调用impl.operationImpl() |
| 解决的问题 | 多继承导致的类爆炸(M×N 个类 → M+N 个类) |
| 与适配器区别 | 桥接:事前设计,抽象与实现分离;适配器:事后补救,接口转换 |
| 与装饰区别 | 桥接:两个独立维度分离;装饰:单一维度层层增强 |
8.2 易混淆辨析:桥接 vs 适配器 vs 装饰
表格
| 对比项 | 桥接模式 | 适配器模式 | 装饰模式 |
|---|---|---|---|
| 类图特征 | 两个独立继承树 + 组合关联 | 一个 Adapter 转换类 | 递归组合结构(Decorator 组合 Component) |
| 维度数量 | 两个独立维度 | 一个转换维度 | 一个增强维度 |
| 设计目的 | 抽象与实现分离,独立扩展 | 接口转换,兼容旧代码 | 动态增强功能 |
| 是否新增功能 | ❌ 不新增(只是分离) | ❌ 不新增(只是转换) | ✅ 新增功能 |
| Client 调用 | new Abstraction(new Implementor()) | new Adapter(new Adaptee()) | new Decorator(new Component()) |
| 软考判断 | 两个继承树 + 组合 | 一个转换类 + 接口不同 | 递归包装 + 接口相同 |
九、真题风格模拟与代码填空
模拟题 1(上午选择题)
以下关于桥接模式的叙述中,正确的是()。
A. 桥接模式属于创建型模式,主要用于创建对象的抽象与实现分离
B. 在桥接模式中,Abstraction 通过继承 Implementor 来获得实现功能
C. 桥接模式的核心思想是将抽象部分与实现部分分离,使它们可以独立变化,从而避免类爆炸
D. 桥接模式与适配器模式没有本质区别,都是用于接口转换的
答案:C
解析:
A 错误:桥接属于结构型模式,不是创建型。
B 错误:Abstraction 通过组合/聚合(持有引用)Implementor,而不是继承。如果用继承,就退化为多继承,失去了桥接的意义。
C 正确:这是桥接模式的核心定义和价值。
D 错误:两者有本质区别。桥接是事前设计(抽象与实现分离),适配器是事后补救(接口转换兼容)。
模拟题 2(下午代码填空 — 补全桥接点)
某系统使用桥接模式实现文件导出功能。抽象层为
Exporter(导出器),实现层为ExportFormat(导出格式)。请补全(1)~(4)。
// 实现层接口 interface ExportFormat { void writeFile(String content, String path); } // 具体实现层:PDF 格式 class PdfFormat implements ExportFormat { public void writeFile(String content, String path) { System.out.println("[PDF] 导出到: " + path); } } // 具体实现层:Excel 格式 class ExcelFormat implements ExportFormat { public void writeFile(String content, String path) { System.out.println("[Excel] 导出到: " + path); } } // 抽象层 abstract class Exporter { // 桥接点:组合实现层 protected (1)______ format; public Exporter((2)______ format) { this.format = format; } public abstract String prepareContent(); public void export(String path) { String content = prepareContent(); (3)______.(4)______(content, path); } } // 扩充抽象层:报表导出器 class ReportExporter extends Exporter { private String[][] data; public ReportExporter(ExportFormat format, String[][] data) { super(format); this.data = data; } public String prepareContent() { StringBuilder sb = new StringBuilder("报表数据\n"); for (String[] row : data) sb.append(String.join(",", row)).append("\n"); return sb.toString(); } } public class Client { public static void main(String[] args) { String[][] data = {{"Q1", "100万"}, {"Q2", "150万"}}; Exporter exporter = new ReportExporter(new PdfFormat(), data); exporter.export("/tmp/report.pdf"); } }答案:
(1)
ExportFormat(2)
ExportFormat(3)
format(4)
writeFile
阅卷要点:
(1) 必须是
ExportFormat(实现层接口类型),体现面向接口编程。不能写PdfFormat或ExcelFormat等具体类。(2) 构造器参数类型必须是
ExportFormat(抽象),因为客户端注入的可能是任何具体实现。(3) 必须是
format(Abstraction 中持有的 Implementor 引用),这是桥接模式的 "桥"。(4) 必须是
writeFile,这是 Implementor 接口定义的方法,Abstraction 将操作委托给它。
模拟题 3(下午代码填空 — 类图识别与补全)
某系统使用桥接模式设计数据库访问层。抽象层为
DatabaseAccessor(访问器),实现层为DatabaseDriver(驱动)。UserAccessor和OrderAccessor继承DatabaseAccessor,MySQLDriver和OracleDriver实现DatabaseDriver。请补全(1)~(3)。
// 实现层接口 interface DatabaseDriver { void connect(String url); void execute(String sql); void close(); } // 抽象层 abstract class DatabaseAccessor { protected (1)______ driver; public DatabaseAccessor((2)______ driver) { this.driver = driver; } public abstract void query(String condition); protected void executeQuery(String sql) { driver.connect("jdbc:..."); driver.execute(sql); driver.close(); } } // 扩充抽象层:用户访问器 class UserAccessor extends DatabaseAccessor { public UserAccessor(DatabaseDriver driver) { super(driver); } public void query(String condition) { String sql = "SELECT * FROM users WHERE " + condition; (3)______; } } public class Client { public static void main(String[] args) { DatabaseAccessor accessor = new UserAccessor(new MySQLDriver()); accessor.query("id = 1001"); } }答案:
(1)
DatabaseDriver(2)
DatabaseDriver(3)
executeQuery(sql)
阅卷要点:
(1)(2) 必须是
DatabaseDriver(实现层接口),不能是具体驱动类。这是桥接模式的核心:抽象层只依赖实现层接口,不依赖具体实现。(3) 必须是
executeQuery(sql),因为UserAccessor将具体查询逻辑委托给父类DatabaseAccessor的通用执行方法,而父类再通过driver委托给具体驱动。体现了 "抽象层定义流程,实现层定义细节" 的分工。
十、常见陷阱与注意事项
陷阱 1:误认为桥接是创建型模式
桥接属于结构型模式,因为它解决的是类与类之间的结构关系(抽象层与实现层的组合分离),而不是对象的创建问题。软考上午题如果问"以下属于创建型模式的是",选项里出现"桥接"不能选。
陷阱 2:桥接点写成继承关系
这是软考最高频的失分点。桥接模式的核心是组合代替继承:
❌ 错误:
class Shape extends RedColor(多继承,类爆炸)✅ 正确:
class Shape { protected Color color; }(组合,独立扩展)
软考代码填空里,Abstraction 与 Implementor 之间必须是组合/聚合关系(protected Implementor xxx),绝不能是继承。
陷阱 3:与适配器模式混淆
这是软考最常考的模式辨析题。
桥接:两个独立继承树,事前设计,目的是分离维度、避免类爆炸。
适配器:一个转换类,事后补救,目的是接口转换、兼容旧代码。
快速判断技巧:看类图里有几个继承树。两个独立继承树 → 桥接;一个继承树 + 一个转换类 → 适配器。
陷阱 4:Implementor 接口设计过细或过粗
Implementor 的接口应该只包含实现层需要提供的原子操作(如applyColor()、doSend()、writeFile()),不应该包含业务逻辑。如果 Implementor 里出现了与 Abstraction 重复的业务方法,说明分离不彻底,违背了桥接模式的设计初衷。
陷阱 5:RefinedAbstraction 直接调用 Implementor
虽然 RefinedAbstraction 可以访问父类的protected Implementor impl字段,但规范的做法是:RefinedAbstraction 只实现自己特有的抽象行为,通用委托逻辑由 Abstraction 统一处理。如果 RefinedAbstraction 里直接写impl.xxx(),虽然功能正确,但代码结构不够优雅,软考阅卷时通常不会扣分,但最佳实践是通过父类方法间接委托。
陷阱 6:构造器注入 vs Setter 注入
软考代码填空里,Abstraction 注入 Implementor 通常通过构造器(如public Shape(Color color)),这样能保证对象创建时实现层就已确定。如果用 Setter 注入(如setColor()),虽然也算对,但构造器注入更符合桥接模式 "创建时绑定" 的设计意图。
十一、总结
| 要点 | 内容 |
|---|---|
| 定义 | 将抽象部分与它的实现部分分离,使它们都可以独立地变化 |
| 分类 | 结构型模式(GoF 23 正式成员) |
| 核心四角色 | Abstraction(抽象)、RefinedAbstraction(扩充抽象)、Implementor(实现接口)、ConcreteImplementor(具体实现) |
| 核心思想 | 组合代替继承:两个独立维度拆分为两个继承树,通过组合关联 |
| 桥接点 | Abstraction 中声明protected Implementor impl,通过构造器注入 |
| 方法委托 | Abstraction 的operation()调用impl.operationImpl() |
| 解决的问题 | 多继承导致的类爆炸(M×N → M+N) |
| 与适配器区别 | 桥接:事前设计,抽象与实现分离;适配器:事后补救,接口转换 |
| 与装饰区别 | 桥接:两个独立维度分离;装饰:单一维度层层增强 |
| 软考重点 | 类图识别(两个独立继承树);代码填空(桥接点声明 + 委托调用);与适配器的辨析 |
| 答题技巧 | 看到 "两个独立维度"、"抽象与实现分离"、"避免类爆炸" → 桥接;看到类图有两个继承树通过组合关联 → 确认桥接 |
系列预告:下一篇将讲组合模式,咱们下回见。