👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 二、桥接模式在框架源码中的运用
- [JDBC(Java 数据库连接)](#JDBC(Java 数据库连接))
- [JDBC 桥接模式工作流程](#JDBC 桥接模式工作流程)
- [JDBC 的源码实现](#JDBC 的源码实现)
- [Spring Web MVC](#Spring Web MVC)
- 三、总结
一、入门
什么是桥接模式?
桥接模式(Bridge Pattern)是一种结构型设计模式 ,核心思想是将抽象与实现分离,让它们可以独立变化。简单来说,它像一座"桥"连接了两个维度的变化,避免用继承导致代码臃肿。
为什么要用桥接模式?
这次我以大家熟悉的支付系统为例,详细说明桥接模式如何解决现实中的问题。假设我们要实现一个支持不同支付方式(支付宝、微信)和不同支付渠道(国内支付、跨境支付)的系统。
传统实现:支付方式 × 渠道 = 类爆炸:以下为代码实现
java
// 支付宝国内支付
class AlipayDomestic {
void pay() {
System.out.println("支付宝国内支付:调用国内风控API");
System.out.println("支付宝国内支付:人民币结算");
}
}
// 支付宝跨境支付
class AlipayCrossBorder {
void pay() {
System.out.println("支付宝跨境支付:调用国际风控API");
System.out.println("支付宝跨境支付:美元结算");
}
}
// 微信国内支付
class WechatDomestic {
void pay() {
System.out.println("微信国内支付:调用国内风控API");
System.out.println("微信国内支付:人民币结算");
}
}
// 微信跨境支付
class WechatCrossBorder {
void pay() {
System.out.println("微信跨境支付:调用国际风控API");
System.out.println("微信跨境支付:美元结算");
}
}
// 如果新增一个银联支付,需要再写两个类...
// 如果新增一个支付渠道(如港澳台专线),需要为每个支付方式创建新类...
存在问题:
- 重复代码:国内/跨境支付的风控和结算逻辑被重复写在每个类中。
- 维护困难:修改国内支付的结算规则,需要改动所有相关类。
- 扩展性差:新增支付方式或渠道时,需要创建大量新类。
如何实现桥接模式?
桥接(Bridge)模式包含以下主要角色:
- 抽象化(Abstraction)角色 :定义抽象类,并包含一个对实现化对象的引用。
- 扩展抽象化(Refined Abstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色 :定义实现化角色的接口,供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
【案例】桥接模式- 改
桥接模式解法:解耦支付方式与渠道
核心思想
- 抽象部分:支付方式(支付宝、微信),关注用户侧操作。
- 实现部分:支付渠道(国内、跨境),关注底层金融操作。
抽象化 (Abstraction) : PaymentMethod
类,定义高层抽象接口,持有实现层对象引用。
java
abstract class PaymentMethod {
protected PaymentChannel channel; // 关键:桥接的核心
public PaymentMethod(PaymentChannel channel) {
this.channel = channel;
}
public final void pay() {
channel.applyRiskControl(); // 调用实现层
doPayment(); // 自身逻辑
channel.settleFunds(); // 调用实现层
}
protected abstract void doPayment();
}
扩展抽象化 (Refined Abstraction) :Alipay
类、WechatPay
类。实现并扩展抽象层的业务逻辑。
java
// 支付宝支付
class Alipay extends PaymentMethod {
public Alipay(PaymentChannel channel) {
super(channel);
}
protected void doPayment() {
System.out.println("支付宝:扫码用户付款码,扣除余额");
}
}
// 微信支付
class WechatPay extends PaymentMethod {
public WechatPay(PaymentChannel channel) {
super(channel);
}
protected void doPayment() {
System.out.println("微信:调用微信SDK,从零钱扣款");
}
}
实现化 (Implementor) :PaymentChannel
接口。定义底层操作的接口规范(如风控和结算),独立于抽象层变化(支付方式如何变化不影响渠道实现)。
java
interface PaymentChannel {
void applyRiskControl(); // 风控接口
void settleFunds(); // 结算接口
}
具体实现化 (Concrete Implementor):DomesticChannel, CrossBorderChannel类。具体实现底层操作(如国内渠道的身份证验证),可独立扩展(新增渠道无需修改抽象层)。
// 国内支付渠道
class DomesticChannel implements PaymentChannel {
public void applyRiskControl() {
System.out.println("国内风控:验证身份证 + 银行卡绑定");
}
public void settleFunds() {
System.out.println("结算:人民币入账,T+1到账");
}
}
// 跨境支付渠道
class CrossBorderChannel implements PaymentChannel {
public void applyRiskControl() {
System.out.println("跨境风控:验证护照 + 外汇管制检查");
}
public void settleFunds() {
System.out.println("结算:美元入账,汇率转换,T+3到账");
}
}
客户端
java
public class Client {
public static void main(String[] args) {
// 创建支付渠道(实现层)
PaymentChannel domestic = new DomesticChannel();
PaymentChannel crossBorder = new CrossBorderChannel();
// 组合支付方式和渠道(抽象层)
PaymentMethod alipayDomestic = new Alipay(domestic);
PaymentMethod wechatCrossBorder = new WechatPay(crossBorder);
// 执行支付
alipayDomestic.pay();
/* 输出:
开始支付流程...
国内风控:验证身份证 + 银行卡绑定
支付宝:扫码用户付款码,扣除余额
结算:人民币入账,T+1到账
支付完成!
*/
wechatCrossBorder.pay();
/* 输出:
开始支付流程...
跨境风控:验证护照 + 外汇管制检查
微信:调用微信SDK,从零钱扣款
结算:美元入账,汇率转换,T+3到账
支付完成!
*/
}
}
这里肯定会有UU说,我好像用装饰器模式也能实现
确实,装饰器模式在某些场景下可能与桥接模式有相似之处,但它们的核心意图和适用场景有本质区别。让我通过对比来帮你理清这两种模式的区别,以及为什么在支付系统的例子中桥接模式更合适。
桥接模式 | 装饰器模式 | |
---|---|---|
核心目的 | 分离抽象与实现,让两个维度独立扩展 | 动态地为对象添加额外功能 |
关系结构 | 组合关系(横向扩展) | 嵌套装饰(纵向扩展) |
适用场景 | 多个正交变化的维度(如形状×渲染方式) | 需要动态叠加功能(如咖啡加糖、加奶) |
设计重点 | 解耦抽象层与实现层 | 增强对象的功能 |
二、桥接模式在框架源码中的运用
JDBC(Java 数据库连接)
JDBC 桥接模式工作流程
加载驱动实现类(具体实现化)
java
Class.forName("com.mysql.cj.jdbc.Driver"); // 触发MySQL驱动的static块注册
获取抽象层接口(桥接器选择具体实现)
java
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb", "user", "password"
);
DriverManager
根据URLjdbc:mysql:
找到MySQL驱动- 调用
MySQL Driver.connect()
方法,返回ConnectionImpl
实例(但应用程序只看到Connection
接口)
操作数据库(通过抽象层接口)
java
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
- 无论底层是MySQL还是Oracle,代码完全一致
JDBC 的源码实现
桥接模式角色 | JDBC 中的对应组件 | 具体职责 |
---|---|---|
抽象化 (Abstraction) | java.sql.Connection等接口 | 定义数据库操作的高层抽象 |
扩展抽象化 (Refined Abstraction) | 无(直接使用接口) | JDBC接口已足够抽象,无需扩展 |
实现化 (Implementor) | java.sql.Driver | 接口 定义数据库驱动实现的规范 |
具体实现化 (Concrete Implementor) | 各数据库驱动实现类(如com.mysql.cj.jdbc.Driver) | 具体实现数据库连接和操作逻辑 |
桥接器 (Bridge) | DriverManager | 连接抽象层和实现层的桥梁 |
抽象化 (Abstraction)
- 对应组件:
java.sql.Connection
、Statement
、PreparedStatement
等接口 - 核心职责:
- 定义数据库操作的抽象接口(如执行SQL、事务管理)
- 完全独立于具体数据库(无论是MySQL还是Oracle,接口方法一致)
java
public interface Connection extends Wrapper, AutoCloseable {
Statement createStatement() throws SQLException;
PreparedStatement prepareStatement(String sql) throws SQLException;
void commit() throws SQLException;
// 其他数据库操作抽象方法...
}
实现化 (Implementor)
- 对应接口:
java.sql.Driver
- 核心职责:
- 定义数据库驱动的规范(如何连接数据库、创建连接对象)
- 由各数据库厂商实现(如MySQL、Oracle、PostgreSQL)
java
public interface Driver {
Connection connect(String url, Properties info) throws SQLException;
boolean acceptsURL(String url) throws SQLException;
// 其他驱动核心方法...
}
具体实现化 (Concrete Implementor)
- 对应类 :各数据库的驱动实现类
MySQL: com.mysql.cj.jdbc.Driver
Oracle: oracle.jdbc.OracleDriver
PostgreSQL: org.postgresql.Driver
- 核心职责 :
- 实现Driver接口,提供具体的数据库连接逻辑
- 隐藏数据库底层差异(如MySQL的协议、Oracle的专有语法)
java
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
// 注册驱动到DriverManager
DriverManager.registerDriver(new Driver());
}
public Connection connect(String url, Properties info) throws SQLException {
// 创建MySQL专属的ConnectionImpl对象
return new ConnectionImpl(url, info);
}
}
桥接器 (Bridge)
- 对应类 :
java.sql.DriverManager
- 核心职责 :
- 管理所有注册的数据库驱动(
Driver
实现类) - 根据URL选择合适的具体驱动,返回抽象层接口(如
Connection
)
- 管理所有注册的数据库驱动(
关键代码逻辑:
java
public class DriverManager {
private static final List<Driver> drivers = new CopyOnWriteArrayList<>();
// 注册驱动
public static void registerDriver(Driver driver) {
drivers.add(driver);
}
// 获取连接(核心桥接逻辑)
public static Connection getConnection(String url, String user, String password) {
for (Driver driver : drivers) {
if (driver.acceptsURL(url)) {
// 调用具体驱动的connect方法
return driver.connect(url, properties);
}
}
throw new SQLException("No suitable driver found for " + url);
}
}
Spring Web MVC
桥接模式的应用
- 抽象层:
HandlerAdapter
(处理器适配器接口) - 实现层:不同处理器类型的适配器(如
RequestMappingHandlerAdapter
)
java
// 抽象层:HandlerAdapter接口
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}
// 实现层:RequestMappingHandlerAdapter
public class RequestMappingHandlerAdapter implements HandlerAdapter {
public boolean supports(Object handler) {
return handler instanceof HandlerMethod;
}
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 处理@RequestMapping注解的方法
}
}
桥接模式的作用
- 解耦:
DispatcherServlet
通过HandlerAdapter
调用处理器,无需关心具体处理器的类型(如@Controller
、HttpRequestHandler
)。 - 扩展性:新增处理器类型只需实现
HandlerAdapter
接口,无需修改DispatcherServlet
。
三、总结
桥接模式的优点
- 解耦抽象与实现
- 抽象层和实现层可以独立变化,互不影响。
- 例如,JDBC中Connection接口(抽象层)和Driver实现(实现层)可以分别扩展。
- 提高扩展性
- 新增抽象类或实现类时,无需修改现有代码,符合开闭原则。
- 例如,新增数据库驱动只需实现Driver接口,无需修改JDBC核心代码。
- 减少类爆炸
- 通过组合代替继承,避免了多层继承导致的类数量暴增。
- 例如,支付系统中支付方式和支付渠道的组合只需M+N个类,而不是M×N个类。
- 运行时动态绑定
- 可以在运行时动态切换实现。
- 例如,JDBC中DriverManager根据URL动态选择具体的数据库驱动。
- 隐藏实现细节
- 客户端只依赖抽象接口,无需关心具体实现。
- 例如,应用程序只使用Connection接口,无需关心底层是MySQL还是Oracle。
桥接模式的缺点
- 增加系统复杂性
- 需要额外设计抽象层和实现层,增加了代码量和理解难度。
- 对于简单场景,可能显得过度设计。
- 设计难度较高
- 需要准确识别系统中独立变化的维度,设计不当可能导致模式滥用。
- 例如,如果错误地将两个强耦合的维度分离,反而会增加维护成本。
- 性能开销
- 由于抽象层和实现层通过组合连接,可能会引入额外的间接调用,带来轻微的性能开销。
桥接模式适用场景
- 多个独立变化的维度
- 当系统中存在两个或多个正交变化的维度,且每个维度都可能独立扩展时。
- 经典案例:
- 支付系统:支付方式 × 支付渠道
- 图形库:形状 × 渲染方式
- 跨平台UI框架:UI组件 × 操作系统API
- 避免多层继承
- 当使用继承会导致类数量爆炸时,桥接模式是更好的选择。
- 经典案例:
- JDBC:Connection接口 × 数据库驱动
- 物流系统:运输方式 × 物流公司
- 运行时切换实现
- 当需要在运行时动态切换实现时。
- 经典案例:
- 日志框架:日志接口 × 日志实现(Logback、Log4j)
- 消息通知系统:通知类型 × 发送渠道(短信、邮件、App推送)
- 隐藏实现细节
- 当需要对外暴露简洁的接口,同时隐藏复杂的实现细节时。
- 经典案例:
- 数据库连接池:连接池接口 × 具体连接池实现(HikariCP、Druid)
- 缓存框架:缓存接口 × 缓存实现(Redis、Memcached)