当前位置: 首页 > news >正文

GoF设计模式——桥接模式

本文是【GoF设计模式】系列第10篇

前言

为什么需要桥接模式?

假设要做一个图形编辑器,有圆形、矩形、三角形三种图形,每种图形又要支持红色、蓝色、黄色三种颜色。用继承来实现,就要写RedCircleBlueCircleRedRectangleBlueRectangle… 类的数量 = 图形数 × 颜色数,3 × 3 = 9 个类。再加一种图形或一种颜色,类就会爆炸式增长。

// 继承方案:类爆炸classRedCircleextendsCircle{...}classBlueCircleextendsCircle{...}classRedRectangleextendsRectangle{...}// ... 9 个类,再加一个图形或颜色就要翻倍

两种选择:要么忍受类爆炸,要么把颜色作为字段写在图形类里——后者看似简单,但每种颜色逻辑都要在图形类内部判断,代码会变得臃肿,加一种颜色就要改所有图形类。

这种"两个维度各自变化,组合后类爆炸"的矛盾,就是桥接模式要解决的问题。

概念

桥接模式(Bridge Pattern)是一种结构型设计模式,核心思想是将抽象与实现分离到两个独立的维度,使它们可以各自独立扩展

桥接模式包含四个角色:

  • Abstraction(抽象):定义抽象部分的接口,持有对实现层的引用(这就是"桥")。通常用抽象类,因为需要持有引用并提供默认逻辑。
  • RefinedAbstraction(修正抽象):对抽象接口进行扩展,是抽象的具体变体。
  • Implementor(实现):定义实现部分的接口,与抽象层的接口可以完全不同。
  • ConcreteImplementor(具体实现):实现实现化接口的具体类。

继承

持有(桥)

实现

实现

«abstract»

Abstraction

-impl: Implementor

+operation()

RefinedAbstraction

+operation()

«interface»

Implementor

+operationImpl()

ConcreteImplementorA

+operationImpl()

ConcreteImplementorB

+operationImpl()

Abstraction 内部持有 Implementor 的引用,这是"桥"的关键——通过组合而非继承连接两个维度。RefinedAbstraction 扩展抽象层,ConcreteImplementorA/B 实现实现层,两边各自独立发展。

可以把桥接模式理解为遥控器和电视的关系:遥控器是抽象层,电视是实现层。遥控器内部持有一个电视的引用,按下"开机"键时调用电视的开机方法。不同的遥控器(基础遥控器、万能遥控器)是修正抽象,不同品牌的电视(小米、索尼)是具体实现。这样遥控器和电视可以各自独立发展——新增一个电视品牌不需要改遥控器的代码,新增一种遥控器也不需要改电视的代码。

实现

桥接模式只有一种实现方式:抽象层持有实现层的引用,通过组合而非继承来关联两个维度。

基础实现

实现步骤:定义实现层接口,创建具体实现类;定义抽象层基类(抽象类),内部持有实现层引用;创建修正抽象类扩展抽象层。

// ===== 实现层接口 =====interfaceImplementor{publicvoidoperationImpl();}// ===== 具体实现A =====classConcreteImplementorAimplementsImplementor{publicvoidoperationImpl(){System.out.println("实现方式A");}}// ===== 具体实现B =====classConcreteImplementorBimplementsImplementor{publicvoidoperationImpl(){System.out.println("实现方式B");}}// ===== 抽象层基类(用抽象类,因为需要持有实现层引用) =====abstractclassAbstraction{protectedImplementorimpl;// 桥:持有实现层的引用publicAbstraction(Implementorimpl){this.impl=impl;}publicvoidoperation(){impl.operationImpl();// 委托给实现层}}// ===== 修正抽象 =====classRefinedAbstractionextendsAbstraction{publicRefinedAbstraction(Implementorimpl){super(impl);}publicvoidoperation(){// 可以在委托前后加自己的逻辑System.out.println("RefinedAbstraction 额外逻辑");super.operation();}}// ===== 客户端 =====publicclassClient{publicstaticvoidmain(String[]args){ImplementorimplA=newConcreteImplementorA();Abstractionabs=newRefinedAbstraction(implA);abs.operation();}}

引入一个例子:「一台万能遥控器,手里有小米电视和索尼电视两台设备。遥控器内部有个"电视插槽",插哪台电视就能控制哪台——遥控器本身不用改代码,电视也不用改接口」。

遥控器对应Abstraction(抽象层),电视对应Implementor(实现层),"插槽"对应构造方法注入的组合关系。换一台电视只需把新实例传给遥控器,两边各自独立。

// 实现层:电视品牌interfaceTV{publicvoidpowerOn();publicvoidpowerOff();}classXiaomiTVimplementsTV{publicvoidpowerOn(){System.out.println("小米电视开机");}publicvoidpowerOff(){System.out.println("小米电视关机");}}classSonyTVimplementsTV{publicvoidpowerOn(){System.out.println("索尼电视开机");}publicvoidpowerOff(){System.out.println("索尼电视关机");}}// 抽象层:遥控器abstractclassRemoteControl{protectedTVtv;// 持有电视的引用(桥)publicRemoteControl(TVtv){this.tv=tv;}publicvoidturnOn(){tv.powerOn();}publicvoidturnOff(){tv.powerOff();}}// 修正抽象:万能遥控器(额外功能)classUniversalRemoteextendsRemoteControl{publicUniversalRemote(TVtv){super(tv);}publicvoidmute(){System.out.println("静音");}}// 使用:遥控器和电视自由组合RemoteControlbasic=newRemoteControl(newXiaomiTV());basic.turnOn();// 控制小米电视UniversalRemoteuniversal=newUniversalRemote(newSonyTV());universal.turnOn();universal.mute();// 万能遥控器额外功能

为什么 Abstraction 用抽象类而不是接口?桥接模式的抽象层需要:

  1. 持有实现层的引用:接口不能有实例字段,只有抽象类才能保存Implementor实例
  2. 提供默认的委托逻辑operation()方法通常直接委托给impl,写在抽象类中避免子类重复代码

简单说:接口定义"能做什么",抽象类定义"怎么做框架"。桥接模式的抽象层需要既有状态(持有引用)又有行为(默认委托),所以用抽象类更合适。

桥接模式与简单组合的区别

桥接模式本质上就是组合,但它和普通的"一个类持有另一个类"有本质区别:

维度简单组合桥接模式
目的复用功能、委托调用分离两个独立变化的维度
结构无约束,随意组合抽象层 + 实现层 + 桥(持有引用)
扩展性通常只有一方可扩展两边各自独立扩展

举个例子:

// 简单组合:订单持有支付方式的引用classOrder{privatePaymentMethodpayment;// 组合:只是委托publicvoidcheckout(){payment.pay(amount);}}

订单和支付方式之间没有"两个维度各自变化"的关系——订单只有一个维度,支付方式也只有一个维度。目的只是"委托支付",不是解决类爆炸。

而桥接模式的图形 + 渲染:

// 桥接:图形持有渲染器的引用abstractclassShape{protectedRendererrenderer;// 桥:维度分离publicvoiddraw(){renderer.render(this);}}

图形是一个维度(圆形、矩形…),渲染是另一个维度(矢量、像素…),两边各自独立扩展——新增一种图形不影响渲染层,新增一种渲染不影响图形层。

判断标准:新增一个实现类,抽象层是否需要新增对应的子类?

  • 简单组合:新增ApplePay,订单类不动——一方扩展,另一方不动
  • 桥接模式:新增渲染方式,图形类不动;新增图形,渲染类不动——两边都可以独立扩展

记忆口诀组合是手段,桥接是结构。组合解决"复用",桥接解决"维度分离"。

总结

桥接模式本质上是用组合替代继承来解决"两个维度各自变化"的问题——把乘法关系的类数量变成加法关系。

什么时候用

  • 一个类有两个独立变化的维度(如图形和颜色、支付方式和优惠策略)
  • 每个维度都可能独立扩展,不希望类数量爆炸
  • 需要在运行时动态切换实现层(如换一个电视品牌)

什么时候不用

  • 只有一个变化维度,用继承或简单组合就够了
  • 两个维度耦合紧密,分开反而增加理解成本
  • 系统简单,过早使用桥接模式会增加复杂度

简单记忆

桥接拆维度,组合替继承。两个方向各自变,类数从乘变加法。

模式接口关系核心意图典型场景
桥接抽象持有实现引用分离两个独立变化的维度图形+渲染、消息+渠道
适配器目标接口 ≠ 被包装对象接口转换接口,让不兼容的类协同第三方库集成
策略上下文持有策略引用替换同一维度的不同算法排序算法、折扣计算
抽象工厂工厂接口定义创建方法创建一系列相关对象跨数据库 DAO

口诀对比:桥接拆维度,适配改接口,策略换算法,工厂管创建。

桥接 vs 适配器

维度桥接模式适配器模式
核心意图事前设计,分离两个独立变化的维度事后补救,让不兼容的接口协同工作
结构差异抽象层持有实现层引用,双方接口可不同适配器实现目标接口,持有被适配对象
关注点预防类爆炸,支持独立扩展解决接口不兼容问题
典型场景JDBC 驱动、跨平台 UI第三方库集成、遗留系统对接

逐步区分法

  • 如果两个维度都可能独立扩展 → 选桥接
  • 如果只是要让现有类协同工作 → 选适配器
  • 如果需要事后补救接口不兼容 → 选适配器

桥接 vs 策略

维度桥接模式策略模式
核心意图分离两个独立变化的维度替换同一维度的不同算法
结构差异抽象层持有实现层引用,两者是平行的维度上下文持有策略引用,策略是算法的变体
关注点维度分离,独立扩展算法替换,行为可变
典型场景图形+渲染、消息+渠道排序算法、折扣计算

逐步区分法

  • 如果有两个独立变化的维度 → 选桥接
  • 如果只是同一个行为的不同实现方式 → 选策略
  • 如果关注点是"用什么算法" → 选策略
  • 如果关注点是"用什么实现体系" → 选桥接

桥接 vs 抽象工厂

维度桥接模式抽象工厂模式
核心意图分离两个独立变化的维度创建一系列相关对象
结构差异抽象层持有实现层引用工厂接口定义创建方法
关注点结构分离,运行时切换对象创建,产品族约束
典型场景JDBC 驱动、跨平台 UI跨数据库 DAO、跨 UI 主题

逐步区分法

  • 如果需要在运行时切换实现 → 选桥接
  • 如果只需要创建一系列相关对象 → 选抽象工厂
  • 如果关注点是"对象怎么创建" → 选抽象工厂
  • 如果关注点是"实现怎么切换" → 选桥接

练习题目

图形渲染系统

题目描述:一个图形系统需要支持多种图形和多种渲染方式。图形包括圆形(Circle)和矩形(Rectangle),渲染方式包括矢量渲染(Vector)和像素渲染(Pixel)。请使用桥接模式实现,使得图形类型和渲染方式可以独立扩展。

输入描述:第一行是一个整数 N(1 ≤ N ≤ 100),表示后面有 N 行输入。接下来的 N 行,每行包含两个字符串,第一个表示图形类型(Circle / Rectangle),第二个表示渲染方式(Vector / Pixel)。

输出描述:对于每行输入,输出该图形使用该渲染方式的绘制结果。

输入示例

6 Circle Vector Circle Pixel Rectangle Vector Rectangle Pixel Circle Vector Rectangle Pixel

输出示例

Drawing Circle with Vector Renderer Drawing Circle with Pixel Renderer Drawing Rectangle with Vector Renderer Drawing Rectangle with Pixel Renderer Drawing Circle with Vector Renderer Drawing Rectangle with Pixel Renderer

解题思路:如果不使用桥接模式,就要为每种组合创建类(VectorCirclePixelCircleVectorRectanglePixelRectangle),类数量 = 图形数 × 渲染数。使用桥接模式,渲染方式是实现层(Renderer接口),图形是抽象层(Shape抽象类持有Renderer引用),通过构造方法注入。这样图形和渲染各自独立扩展,类数量从乘法变成加法。

importjava.util.*;publicclassMain{publicstaticvoidmain(String[]args){Scannersc=newScanner(System.in);intn=sc.nextInt();while(n-->0){StringshapeType=sc.next();StringrendererType=sc.next();// 实现层:渲染方式Rendererr=null;if("Vector".equals(rendererType)){r=newVector();}else{r=newPixel();}// 抽象层:图形(持有渲染器引用)Shapes=null;if("Circle".equals(shapeType)){s=newCircle(r);}else{s=newRectangle(r);}s.draw();}}}// 实现层接口:渲染器interfaceRenderer{publicStringgetName();}classVectorimplementsRenderer{publicStringgetName(){return"Vector Renderer";}}classPixelimplementsRenderer{publicStringgetName(){return"Pixel Renderer";}}// 抽象层:图形(持有渲染器引用)abstractclassShape{protectedRendererrenderer;protectedStringname;publicShape(Stringname,Rendererrenderer){this.name=name;this.renderer=renderer;}publicvoiddraw(){System.out.println("Drawing "+name+" with "+renderer.getName());}}classCircleextendsShape{publicCircle(Rendererr){super("Circle",r);}}classRectangleextendsShape{publicRectangle(Rendererr){super("Rectangle",r);}}

扩展:实际项目中的桥接模式

JDBC 驱动架构

JDBC 是桥接模式最经典的应用。ConnectionStatement等接口是抽象层,各数据库厂商的驱动(MySQL Connector、PostgreSQL Driver)是实现层。应用代码只依赖 JDBC 接口,不关心底层是哪个数据库——MySQL 升级驱动版本不影响业务代码,切换数据库只需换驱动和连接字符串。

消息通知系统

通知系统需要支持多种消息类型(普通、紧急、营销)和多种发送渠道(短信、邮件、微信)。用桥接模式,消息类型是抽象层,发送渠道是实现层。新增一种消息类型只需加一个Message子类,新增一种发送渠道只需加一个MessageSender实现,两者互不影响。

日志框架的 Appender

SLF4J + Logback 中,Logger 是抽象层,Appender(输出目标)是实现层。一个 Logger 可以配置多个 Appender,日志同时输出到控制台、文件、远程服务。新增异步 Logger 不影响 Appender,新增 Kafka Appender 不影响 Logger。

跨平台 UI 组件

UI 框架中,按钮是一个维度,操作系统是另一个维度。同一种按钮在不同操作系统上的渲染方式不同。用桥接模式,UI 组件是抽象层,操作系统渲染是实现层。新增下拉框不影响操作系统层,新增 Linux 支持不影响 UI 组件层。

支付方式与优惠策略

电商系统中,支付方式(支付宝、微信)是一个维度,优惠策略(无优惠、满减、折扣)是另一个维度。用桥接模式,订单是抽象层,支付方式是实现层。新增 Apple Pay 不影响优惠策略,新增"首单立减"不影响支付方式。

现在可能还在写简单的业务代码,但等到遇到"两个维度都要扩展"的场景时——与其让类数量爆炸,不如用桥接模式把它们拆开,各自独立发展。那时候就真的懂了。

http://www.cnnetsun.cn/news/2852516.html

相关文章:

  • 互联网大厂 Java 求职面试实录:从音视频场景到微服务的探讨
  • 【2026最新】降AI率抄作业:97%→7%的完整方法论,亲测有效直接搬
  • 终极文件提取方案:UniExtract2 支持500+格式的万能解包工具
  • 华硕笔记本性能调校新选择:如何用G-Helper告别臃肿控制软件
  • shmem共享内存管理库完全指南:从核心概念到实战应用的系统性入门
  • 模块化小说下载系统架构深度解析与实战实现方案
  • 给开发者的可信计算入门:抛开晦涩规范,用‘信任链’和‘钩子’理解TPM/TPCM到底在干嘛
  • 2025-2026手机解压RAR工具深评
  • 终极指南:3329条专业翻译,让MASA模组全家桶彻底告别英文界面困扰
  • 粉笔事业单位和华图哪个好?事业编备考看公基、职测、综应和模考复盘
  • 不用买服务器!用家里旧电脑+花生壳内网版,5步搞定个人网站(附IIS配置避坑点)
  • 【Kafka源码解读和使用指南】第28篇:ConsumerCoordinator源码解析——消费者与GroupCoordinator的“谈判桌“
  • Ultralytics发布YOLO26:让实时视觉检测更快更准的新“千里眼“
  • 保姆级教程:在Windows/Linux上快速下载并验证nuScenes数据集(附完整文件结构解析)
  • BiliTools 2026终极指南:跨平台B站资源下载与管理的完整解决方案
  • 朗禾品牌设计,深耕餐饮VI与空间设计,以专业实力赋能品牌成长
  • Python 爬虫实战:排行榜榜单数据自动抓取更新
  • 如何快速搭建高效音乐API服务器:LX Music Python版完整实战指南
  • 3分钟掌握Python通达信数据接口:Mootdx快速入门完全指南
  • Palworld《幻兽帕鲁》 服务器搜不到怎么办?端口和防火墙排查清单
  • ARM Cortex-M4微控制器低功耗设计与外设应用实战解析
  • 【LeetCode刷题日记】90.子集Ⅱ--- 归纳题解
  • dotnet-repl完全指南:打造你的多语言.NET命令行交互环境
  • LeetDown终极指南:在macOS上为旧款iPhone/iPad实现系统降级的完整方案
  • Linux——管理SELinux安全性
  • Keyboard Chatter Blocker:告别机械键盘连击困扰的智能解决方案
  • 高级技巧:R-GCN中的基分解(Basis Decomposition)机制详解
  • Fleek跨平台环境同步教程:在Mac、Linux和WSL间无缝切换
  • 嵌入式硬件设计:Kinetis K28F MCU引脚配置、封装选型与PCB设计实践
  • 终极指南:如何用eqMac免费解锁macOS专业级音频控制