走进 Gang of Four 设计模式:桥接模式

发布时间:2026/7/16 4:50:33
走进 Gang of Four 设计模式:桥接模式 走进 Gang of Four 设计模式桥接模式说明不仅告诉你怎么用更告诉你为什么这样设计前置知识Java 面向对象基础接口、抽象类、多态、组合、继承与组合的区别 设计模式分类总览GoF 23 种设计模式可按两个维度交叉分类类/对象处理方式 ×创建型/结构型/行为型目的。维度创建型Creational结构型Structural行为型Behavioral类Class通过继承复用Factory Method工厂方法Adapter类适配器Interpreter解释器Template Method模板方法对象Object通过组合/聚合复用Abstract Factory抽象工厂Builder建造者Prototype原型Singleton单例Adapter对象适配器Bridge桥接Composite组合Decorator装饰Facade外观Flyweight享元Proxy代理Chain of Resp.责任链Command命令Iterator迭代器Mediator中介者Memento备忘录Observer观察者State状态Strategy策略Visitor访问者本文档聚焦结构型-对象模式中的Bridge桥接核心思想是用组合代替继承来解决多维度扩展问题。 目录模式概述 · 15 问深度分析经典实现画不同颜色的圆完整案例消息发送桥接源码实战JDBC 桥接深度追问Why / How / Trade-off / Evolution / Modern Practice总结参考文献1. 模式概述 · 15 问深度分析Q1: 为什么需要这个模式它解决了什么问题桥接模式解决的核心问题是多维度变化导致的类爆炸。在软件开发中一个对象经常存在两个或多个独立变化的维度。例如形状有圆形/矩形颜色有红色/绿色。如果用继承来表达这种交叉关系类的数量呈乘法级增长。核心痛点当系统可能从多个角度进行分类且每个角度都可能独立变化时传统继承会带来灾难性的类爆炸和维护成本。Q2: 如果不用这个模式会有什么缺陷如果不引入桥接模式我们通常会使用传统的类继承来处理多维度扩展。这会带来两个巨大的缺陷类爆炸Class Explosion假设你要实现不同颜色的形状有 3 种形状矩形、圆形、三角形和 3 种颜色红色、绿色、蓝色。如果用继承你需要写 3×39 个子类如RedCircle,GreenCircle等。如果再加一种形状就要多写 3 个类再加一种颜色又要多写 4 个类。类的数量呈乘法级增长。高耦合与违反 SRP单一职责原则子类既负责形状的逻辑又负责颜色的逻辑两者紧紧绑定在一起无法独立复用。// ❌ 继承方案3 种形状 × 3 种颜色 9 个类classRedCircleextendsShape{/* 红色圆形逻辑 */}classGreenCircleextendsShape{/* 绿色圆形逻辑 */}classBlueCircleextendsShape{/* ... */}classRedSquareextendsShape{/* ... */}classGreenSquareextendsShape{/* ... */}// ... 每增加一种形状或颜色都需要增加一堆子类Q3: 核心思想是什么一句话如何概括核心思想用组合/聚合代替继承将多维度的变化解耦。一句话概括将抽象化与实现化脱耦使二者可以独立变化。Q4: 包含哪些角色每个角色的职责是什么桥接模式通过将继承关系转化为关联关系将原本的一体拆分为两个独立变化的维度抽象化Abstraction和实现化Implementor。角色名称职责Abstraction抽象化角色定义抽象类的接口维护一个指向Implementor实现化对象的引用RefinedAbstraction修正抽象化角色抽象化角色的子类实现并扩展父类业务逻辑通常在此处调用底层实现化角色的方法Implementor实现化角色定义实现化维度的接口这个接口不一定要和Abstraction的接口一致通常它只提供基本操作ConcreteImplementor具体实现化角色真正实现Implementor接口提供底层维度的具体功能Q5: 它们之间如何协作调用流程是怎样的客户端Client创建一个具体的底层实现对象ConcreteImplementor比如RedColor。客户端将这个实现对象注入到高层的抽象对象RefinedAbstraction中比如Circle。客户端调用高层对象的业务方法如shape.draw()。高层对象在执行自己逻辑的同时通过持有的引用回调底层对象的方法如color.applyColor()协同完成任务。Q6: 为什么要这样设计每个角色存在的意义是什么Abstraction 的意义它是面向用户的顶层接口如形状、“消息级别”。用户不需要关心底层技术细节只需要和抽象打交道。Implementor 的意义它是底层的技术实现如绘图引擎、“发送渠道”。它把具体的执行细节抽离出来不让其污染高层的业务逻辑。Q7: 为什么使用接口、抽象类、组合而不是其他方式为什么用组合继承是生生死死的强耦合Is-A在编译期就确定了而组合是搭伙过日子的弱耦合Has-A可以在运行期动态指定和更换。为什么用抽象类Abstraction因为它需要持有一个Implementor的成员变量并且需要提供通用的构造函数来注入这个变量。为什么用接口Implementor因为底层实现往往是一组纯粹的行为规范用接口最符合面向接口编程的定义。Q8: 这种设计符合哪些面向对象原则开闭原则OCP极佳。你想增加一种形状只需继承Abstraction想增加一种颜色只需实现Implementor。两边扩展都不需要修改现有代码。单一职责原则SRP形状类只负责形状的逻辑颜色类只负责颜色的逻辑各司其职。依赖倒置原则DIP高层的Abstraction依赖的是Implementor接口而不是具体的ConcreteImplementor实现了面向抽象编程。Q9: 它有哪些优点和局限性会带来哪些额外成本优点抽象和实现分离极大地提高了扩展性用组合加法代替了继承乘法控制了类体积可以在运行时动态切换实现局限性/额外成本增加了系统的理解与设计难度。由于高层需要聚合底层开发者必须在设计初期就准确识别出系统中哪些是独立变化的维度。如果轴线划分错误重构的代价极大。Q10: 它最适合哪些场景哪些场景不应该使用适合使用一个类存在两个或多个独立变化的维度且这两个维度都需要扩展。系统不希望在抽象和实现之间有固定的绑定关系需要运行时动态切换。典型的例子跨平台操作系统的 GUI 驱动、不同格式的报表输出在不同介质上、不同类型的消息通过不同的渠道发送如特急/普通消息 × 短信/邮件。不应该使用如果系统只有单一的变化维度或者各个维度之间紧密相关、无法分离使用桥接只会徒增复杂度。Q11: 它与容易混淆的其他设计模式有什么区别和联系与策略模式Strategy联系两者都利用了组合。区别策略模式通常是一维的变化目的在于替换算法/行为行为型而桥接模式是多维的变化目的在于分离结构与实现结构型它的抽象部分本身也有一套继承体系。与适配器模式Adapter区别适配器是事后补救用于把两个不兼容的现有接口协同工作桥接模式是事前设计在设计之初就故意把抽象和实现拆开。Q12: Java/JDK、Spring、MyBatis 等框架中有哪些典型应用JDBC (Java Database Connectivity)这是最经典的桥接模式。java.sql.DriverManager和Connection接口充当了抽象和高层接口。各大数据库厂商提供的驱动如com.mysql.cj.jdbc.Driver就是具体实现化角色。Java 的 JDBC 框架把数据库操作抽象与具体数据库的底层协议实现完美桥接。Logback / SLF4JLogger的抽象与具体的Appender输出到控制台、文件、网络也是一种桥接。Q13: 如何从零实现一个最小可运行版本以不同类型的消息通过不同渠道发送为例// 1. Implementor实现化角色发送渠道interfaceMessageImplementor{voidsend(Stringmessage,StringtoUser);}// 2. ConcreteImplementor具体实现短信渠道classSMSImplementorimplementsMessageImplementor{publicvoidsend(Stringmessage,StringtoUser){System.out.println([短信] 发送给 toUser: message);}}// 3. ConcreteImplementor具体实现邮件渠道classEmailImplementorimplementsMessageImplementor{publicvoidsend(Stringmessage,StringtoUser){System.out.println([邮件] 发送给 toUser: message);}}// 4. Abstraction抽象化角色消息类型abstractclassAbstractMessage{// 关键持有了实现化的引用桥梁protectedMessageImplementorimpl;publicAbstractMessage(MessageImplementorimpl){this.implimpl;}publicabstractvoidsendMessage(Stringmessage,StringtoUser);}// 5. RefinedAbstraction修正抽象化普通消息classCommonMessageextendsAbstractMessage{publicCommonMessage(MessageImplementorimpl){super(impl);}publicvoidsendMessage(Stringmessage,StringtoUser){impl.send(message,toUser);}}// 6. RefinedAbstraction修正抽象化加急消息classUrgentMessageextendsAbstractMessage{publicUrgentMessage(MessageImplementorimpl){super(impl);}publicvoidsendMessage(Stringmessage,StringtoUser){StringdecoratedMessage【加急】message;impl.send(decoratedMessage,toUser);}}// Client 运行publicclassBridgeDemo{publicstaticvoidmain(String[]args){// 运行时动态组合用短信发加急消息AbstractMessagemessagenewUrgentMessage(newSMSImplementor());message.sendMessage(服务器宕机了,张三);// 运行时切换用邮件发普通消息AbstractMessagemessage2newCommonMessage(newEmailImplementor());message2.sendMessage(周报记得交。,李四);}}Q14: 如何在现有项目中识别可以使用该模式的地方当你在项目代码中看到以下坏味道时就可以考虑重构为桥接模式出现了形如MysqlUserDao、OracleUserDao、MysqlRoleDao、OracleRoleDao的类命名其中既包含了业务实体维度User, Role又包含了存储介质维度Mysql, Oracle。类的继承树很深且底层子类的名字往往是两个形容词的拼接如RedCircle、GreenSquare。Q15: 如果重新设计这个模式在今天会有哪些改进函数式接口Functional Interface化如果Implementor只有一个核心方法如send在 Java 8 中它可以进化为一个FunctionalInterface。Abstraction在构造时不需要接收一个实现类直接接收一个Lambda 表达式即可AbstractMessagemsgnewUrgentMessage((text,user)-System.out.println(发送text));依赖注入DI的完美融合在 Spring 生态中我们不需要手动new UrgentMessage(new SMSImplementor())。可以利用Qualifier或配置类通过 IoC 在容器启动或运行时动态地把具体的Implementor注入到Abstractionbean 中。微服务/中台架构下的映射在分布式中台设计中高层抽象可能是一个微服务聚合业务层而底层实现是通过 RPC如 Dubbo/gRPC调用的不同中台能力提供方。桥接模式的思想演变为BFF 层负责抽象业务逻辑通过 RPC 桥接不同的底层微服务能力。2. 经典实现画不同颜色的圆2.1 定义桥接实现接口publicinterfaceDrawAPI{voiddrawCircle(intradius,intx,inty);}2.2 具体实现类publicclassRedCircleimplementsDrawAPI{OverridepublicvoiddrawCircle(intradius,intx,inty){System.out.println(Drawing Circle[ color: red, radius: radius, x: x, y]);}}publicclassGreenCircleimplementsDrawAPI{OverridepublicvoiddrawCircle(intradius,intx,inty){System.out.println(Drawing Circle[ color: green, radius: radius, x: x, y]);}}2.3 创建抽象类publicabstractclassShape{protectedDrawAPIdrawAPI;// 持有实现化角色的引用——这就是桥梁protectedShape(DrawAPIdrawAPI){this.drawAPIdrawAPI;}publicabstractvoiddraw();}2.4 扩展抽象类publicclassCircleextendsShape{privateintx,y,radius;publicCircle(intx,inty,intradius,DrawAPIdrawAPI){super(drawAPI);this.xx;this.yy;this.radiusradius;}Overridepublicvoiddraw(){drawAPI.drawCircle(radius,x,y);// 委派给底层实现}}2.5 客户端publicclassBridgePatternDemo{publicstaticvoidmain(String[]args){ShaperedCirclenewCircle(100,100,10,newRedCircle());ShapegreenCirclenewCircle(100,100,10,newGreenCircle());redCircle.draw();greenCircle.draw();}}2.6 运行结果Drawing Circle[ color: red, radius: 10, x: 100, 100] Drawing Circle[ color: green, radius: 10, x: 100, 100]3. 完整案例消息发送桥接上面的消息发送示例展示了桥接模式在真实业务中的应用。关键点是增加新的消息类型如UrgentMessage只需扩展AbstractMessage侧增加新的发送渠道如微信只需实现MessageImplementor侧两者在运行时可自由组合new UrgentMessage(new WechatImplementor())类数量对比传统继承方案M 种消息类型 × N 种渠道 M×N 个类桥接方案M 种消息类型 N 种渠道 MN 个类这就是桥接模式的数学本质——用加法代替乘法。4. 源码实战JDBC 桥接JDBC 是桥接模式最经典的工业级应用。当你用 JDBC 连接数据库时你就在无意识地使用桥接模式// 客户端代码Abstraction 的使用者ConnectionconnDriverManager.getConnection(jdbc:mysql://localhost:3306/db,user,pass);Statementstmtconn.createStatement();ResultSetrsstmt.executeQuery(SELECT * FROM users);在这个场景中角色JDBC 对应Abstractionjava.sql.Connection、Statement接口RefinedAbstractionJDBC 规范的子接口或封装类Implementor各数据库厂商的 JDBC 驱动内部接口ConcreteImplementorMySQL 的ConnectionImpl、Oracle 的OracleConnection关键点你的业务代码完全不关心底层连的是 MySQL 还是 Oracle。你可以通过更换 JDBC URL 和驱动 jar 包来无缝切换数据库这就是桥接模式带来的抽象与实现分离的威力。5. 深度追问5.1 Why为什么解决哪个根本矛盾桥接模式解决的根本矛盾是类继承机制的静态单一性与业务实体多维度演化之间的矛盾。传统类继承是一种编译期绑定的静态机制它假定类的演化只有一条主线一维空间。但现实中的业务对象往往是在多维空间中交叉演化的。当消息不仅分普通/加急业务等级维度还分短信/邮件技术渠道维度时如果强行用继承去表达这种交叉关系就会迫使多维空间在单一轴线上强行展开导致组合爆炸。桥接模式的本质就是将**业务抽象维度与技术实现维度彻底剥离用运行期的聚合Has-A“打破编译期的继承Is-A”**把类图的增长速度从O(M×N)的乘法灾难降低到O(MN)的加法线性增长。5.2 How怎么做对象关系与交互机制桥接模式通过**“面向接口编程” “运行时依赖注入组合”**的交互机制来解决问题。------------------- ----------------------- | Abstraction | | Implementor | ------------------- ----------------------- | - impl:Implementor|| operationImpl() | | operation() | (组合/委派关联桥梁) ----------------------- ------------------- ^ ^ | | (业务维度继承) | (技术维度实现) ---------------- ---------------- | | | | --------------- --------------- --------------- --------------- |RefinedAbs_A | |RefinedAbs_B | |ConcreteImpl_1 | |ConcreteImpl_2 | --------------- --------------- --------------- ---------------核心交互机制是委派Delegation结构建立运行时客户端在创建高层业务对象RefinedAbstraction时通过构造函数或 Setter 将底层的技术驱动ConcreteImplementor动态注入进去。职责分工调用时高层抽象负责处理业务域的逻辑如权限校验、日志记录、状态流转。当高层业务需要执行具体的底层技术动作时它不自己实现而是将请求委派给它持有的底层接口impl.operationImpl()。隔离效果高层不知道、也不关心具体是哪个类在执行底层动作。两边的代码可以在各自的通道里独立修改、独立编译互不干扰。5.3 Trade-off代价牺牲了什么换取什么它牺牲了付出代价代码直观性与可读性间接性增加传统的类继承非常直观UrgentSMSMessage一看就知道是加急短信。引入桥接后代码被拆分到了两个不同的类继承树中开发者无法一眼看出完整的调用链必须通过运行时依赖分析才能还原业务全貌。初期设计成本开发者必须在系统设计初期就具备极强的抽象能力准确识别出哪些是业务抽象轴哪些是技术实现轴。如果轴线划分错误重构的代价极大。它换取了获得收益无限的横向扩展能力极高灵活性新增一个业务维度或者技术维度现有代码完全不需要动完美的开闭原则。动态可配置性运行时切换可以在不重启系统的情况下通过配置把高层对象绑定的底层实现从 A 切换到 B。独立测试性业务逻辑和底层技术实现可以分别进行单元测试通过 Mock 对方接口即可轻松解耦。5.4 Evolution演化从何而来走向何方原始设计单体臃肿类最早期所有逻辑写在一个类里。当业务变复杂时类内部充斥着各种if-else或switch-case例如if (channel SMS) { ... } else if (channel EMAIL) { ... }。演化第一阶段硬编码继承一维化为了消除if-else引入继承产生SMSMessage和EmailMessage。此时只解决了一个维度的变化。演化第二阶段多重继承/类爆炸多维化引入当引入加急概念时开始向纵深发展出现了UrgentSMSMessage、NormalSMSMessage、UrgentEmailMessage……此时体系开始失控维护成本极高。演化第三阶段桥接模式组合代替继承发现继承走不通GoF 提出桥接模式将加急/普通留在左边将短信/邮件抽到右边中间架起一座组合的桥梁。后续变体与泛化多维桥接Multi-dimensional Bridge如果有三个维度变化如消息等级 × 发送渠道 × 加密算法抽象类可以同时持有多个不同的Implementor接口引用。嵌套桥接桥接模式的Implementor内部可能又是另一个桥接模式的Abstraction。5.5 Modern Practice现代实践它被取代了吗结论GoF 定义的标准桥接模式在形式上确实被弱化或部分隐式取代了但它的核心思想已经融入到了基础设施和现代范式中变成了无处不在的常识。在 Spring / IoC 依赖注入中在传统的桥接模式中我们需要自己写一个AbstractMessage并在构造函数里接收Implementor。在 Spring 中我们通常直接使用面向接口编程 自动注入AutowiredServicepublicclassOrderService{// 相当于 AbstractionAutowiredprivatePaymentProcessorpaymentProcessor;// 相当于 Implementor 接口publicvoidcreateOrder(){// 业务逻辑 ...paymentProcessor.pay();// 委派}}结论Spring IoC 容器接管了桥接模式中桥梁的搭建依赖注入工作。我们不再需要为了桥接而特意写一层复杂的AbstractMessage抽象类。在函数式编程中当底层维度Implementor只有一个核心行为时整个右侧的接口和实现类体系可以被一个函数式接口如Consumer或 Lambda 表达式直接取代。// 现代 Java不需要写右侧的 Implementor 体系publicclassMessageService{publicvoidsendMessage(Stringmsg,BiConsumerString,Stringsender){Stringenriched【通知】msg;sender.accept(enriched,UserA);}}在微服务架构中BFF (Backend For Frontend) / 聚合服务充当了Abstraction的角色负责编排复杂的、面向用户的业务流。底层原子微服务如支付服务、短信服务充当了ConcreteImplementor。BFF 通过 RPC (Dubbo, gRPC) 或 HTTP 接口动态地将业务请求分发给不同的底层微服务。总结桥接模式作为一种类结构在现代开发中由于Spring 依赖注入和Lambda 表达式的普及写起来越来越不明显但它将业务意图What与技术实现How在运行期动态解耦的核心哲学依然是现代微服务、中台架构和整洁架构的基石。6. 总结核心要点桥接模式用组合代替继承将多维度的变化解耦类的数量从乘法级O(M×N)降为加法级O(MN)核心机制是委派Delegation高层对象将底层操作委派给持有的接口引用与适配器的区别适配器是事后补救让不兼容的接口协同工作桥接是事前设计刻意拆分为两个维度选型建议场景推荐做法两个维度独立变化且都会扩展桥接模式需要运行时动态切换实现桥接模式 依赖注入只有单一维度变化普通继承或多态即可不需要桥接不明确哪些是独立维度先写简单方案出现类爆炸时再重构为桥接一句话桥接模式的核心是将维度从继承中解放出来当继承让类的数量不受控制地膨胀时桥接就是那把组合代替继承的手术刀。7. 参考文献[1] GAMMA E, HELM R, JOHNSON R, et al. Design Patterns: Elements of Reusable Object-Oriented Software[M]. Boston: Addison-Wesley, 1994.[2] BLOCH J. Effective Java[M]. 3rd ed. Boston: Addison-Wesley, 2018.[3] SPRING. Spring Framework Documentation[EB/OL]. https://docs.spring.io/spring-framework/, 2024.