设计模式——桥接设计模式(结构型)

article/2025/7/31 13:30:12

摘要

桥接设计模式是一种结构型设计模式,用于将抽象与实现解耦,使二者可以独立变化。它通过将一个类拆分为“抽象”和“实现”两部分,并通过桥接关系组合,避免了类继承层次结构过于庞大。桥接模式包含抽象类、扩充抽象类、实现类接口和具体实现类等角色。在实现方式上,结合了策略模式,适用于风控系统通知等场景。桥接模式适合维度较多的场景,与其他设计模式有明显区别,实战示例展示了其在项目中的应用和优势。

1. 桥接设计模式定义

桥接模式将抽象与其实现解耦,使二者可以独立地变化。

  • 将一个类拆分为“抽象 Abstraction”和“实现 Implementor”两部分,通过桥接关系(Bridge)进行组合。
  • 它主要用于避免类继承层次结构过于庞大,适合维度较多的场景。

1.1. 🔧 关键角色

角色

说明

Abstraction

抽象类,定义高层接口,包含 Implementor 引用

RefinedAbstraction

扩展 Abstraction,具体业务操作

Implementor

实现接口,定义底层实现的规范

ConcreteImplementor

具体实现类,实现底层逻辑

2. 桥接设计模式结构

桥接模式包含如下角色:

  • Abstraction:抽象类
  • RefinedAbstraction:扩充抽象类
  • Implementor:实现类接口
  • ConcreteImplementor:具体实现类

2.1. 桥接设计模式类图

2.2. 桥接设计模式时序图

3. 桥接设计模式实现方式

下面是一个完整的 Spring Boot 项目示例,结合了策略模式 + 桥接模式,实现一个可扩展的风控告警通知系统:根据不同通知类型(策略)选择不同消息类型(桥接抽象)和不同发送方式(桥接实现)。

3.1. 示例需求背景:风控系统通知

  • 风控系统会发送不同级别的通知(普通、加急、特急) → 使用策略模式选择。
  • 每种通知可以通过不同的渠道发送(短信、邮件、钉钉) → 使用桥接模式解耦抽象与实现。

3.2. 📦 项目结构

src
└── main└── java└── com.example.notification├── strategy│   ├── MessageStrategy.java│   ├── NormalMessageStrategy.java│   ├── UrgentMessageStrategy.java│   └── MessageStrategyContext.java├── bridge│   ├── Message.java│   ├── MessageSender.java│   ├── SmsSender.java│   ├── EmailSender.java│   └── DingTalkSender.java├── controller│   └── NotifyController.java└── NotificationApplication.java

3.3. 🧩 桥接设计模式部分(抽象 + 实现)

3.3.1. ✅ MessageSender.java(实现接口)

package com.example.notification.bridge;public interface MessageSender {void send(String content, String toUser);
}

3.3.2. ✅ SmsSender.java / EmailSender.java / DingTalkSender.java(桥接实现)

@Component("smsSender")
public class SmsSender implements MessageSender {public void send(String content, String toUser) {System.out.println("【短信】发送给 " + toUser + ": " + content);}
}@Component("emailSender")
public class EmailSender implements MessageSender {public void send(String content, String toUser) {System.out.println("【邮件】发送给 " + toUser + ": " + content);}
}@Component("dingSender")
public class DingTalkSender implements MessageSender {public void send(String content, String toUser) {System.out.println("【钉钉】发送给 " + toUser + ": " + content);}
}

3.3.3. ✅ Message.java(抽象类)

public abstract class Message {protected final MessageSender sender;public Message(MessageSender sender) {this.sender = sender;}public abstract void send(String content, String toUser);
}

3.4. 🎯 策略模式部分(选择通知类型)

3.4.1. ✅ MessageStrategy.java(策略接口)

public interface MessageStrategy {void send(String content, String toUser);
}

3.4.2. ✅ NormalMessageStrategy.java / UrgentMessageStrategy.java(具体策略)

@Component("normal")
public class NormalMessageStrategy implements MessageStrategy {private final MessageSender sender;public NormalMessageStrategy(@Qualifier("smsSender") MessageSender sender) {this.sender = sender;}@Overridepublic void send(String content, String toUser) {new Message(sender) {@Overridepublic void send(String content, String toUser) {sender.send(content, toUser);}}.send(content, toUser);}
}@Component("urgent")
public class UrgentMessageStrategy implements MessageStrategy {private final MessageSender sender;public UrgentMessageStrategy(@Qualifier("emailSender") MessageSender sender) {this.sender = sender;}@Overridepublic void send(String content, String toUser) {new Message(sender) {@Overridepublic void send(String content, String toUser) {sender.send("[加急] " + content, toUser);}}.send(content, toUser);}
}

3.4.3. ✅ MessageStrategyContext.java(策略上下文)

@Component
public class MessageStrategyContext {private final Map<String, MessageStrategy> strategyMap;public MessageStrategyContext(Map<String, MessageStrategy> strategyMap) {this.strategyMap = strategyMap;}public MessageStrategy getStrategy(String type) {return strategyMap.getOrDefault(type, strategyMap.get("normal"));}
}

3.5. 🎮 控制器 NotifyController.java

@RestController
@RequestMapping("/notify")
public class NotifyController {private final MessageStrategyContext strategyContext;public NotifyController(MessageStrategyContext strategyContext) {this.strategyContext = strategyContext;}@GetMappingpublic String notify(@RequestParam String level,@RequestParam String toUser,@RequestParam String content) {MessageStrategy strategy = strategyContext.getStrategy(level);strategy.send(content, toUser);return "通知发送成功";}
}

3.6. 🚀 启动类 NotificationApplication.java

@SpringBootApplication
public class NotificationApplication {public static void main(String[] args) {SpringApplication.run(NotificationApplication.class, args);}
}

3.7. ✅ 示例调用

  • GET /notify?level=normal&toUser=风控专员&content=用户触发高风险交易
【短信】发送给 风控专员: 用户触发高风险交易
  • GET /notify?level=urgent&toUser=主管&content=系统异常
【邮件】发送给 主管: [加急] 系统异常

3.8. ✅ 策略+桥接+Spring总结

优势

策略模式

动态选择消息类型(策略)

桥接模式

解耦消息类型与发送渠道

Spring 容器

自动注入,支持配置和扩展

易扩展

添加新策略或新渠道无需改动原有逻辑

控制灵活

运行时根据参数选择实现逻辑

4. 桥接设计模式适合场景

4.1. ✅ 适合使用桥接模式的场景

场景

说明

抽象和实现需要独立扩展

比如:消息系统中消息类型(普通、加急) 和 发送方式(短信、邮件)都可能单独增加。桥接模式通过组合解决“类爆炸”问题。

系统存在多维度变化

如设备类型 × 通信协议;风控规则类型 × 数据来源;UI控件 × 渲染方式等。桥接模式适用于“横向+纵向”组合扩展。

不希望抽象类绑定具体实现

抽象类只依赖实现接口,真正的实现类通过组合传入,提高灵活性和可测试性。

需要运行时动态切换实现

实现可以作为参数传入抽象类或在运行时替换,方便配置化和策略组合。

使用组合优于继承的场景

继承会导致层级复杂、类爆炸,桥接通过组合简化层次结构。

4.2. ❌ 不适合使用桥接模式的场景

场景

原因

抽象和实现之间不存在独立扩展需求

如固定只有一种实现方式,没有变化维度,引入桥接反而增加复杂度。

类结构稳定、扩展需求低

桥接增加了层次结构,若业务不会频繁扩展,实现过度设计。

仅仅是想复用实现类

用组合/继承/策略即可,不一定非用桥接模式。

没有多个维度变化(组合笛卡尔积)问题

如果只是单一维度扩展,例如“多种支付方式”,策略模式或工厂模式更合适。

对性能要求极高的底层系统

桥接带来额外抽象层(如接口调用)在某些场景下不如直接调用高效。

4.3. 📌 桥接模式 vs 其他设计模式

模式

区别

策略模式

聚焦在运行时选择行为,但不强调“抽象-实现分离”。

装饰器模式

动态增强功能,适用于功能叠加,不用于解耦抽象与实现。

抽象工厂

用于一组产品族的创建,而桥接强调结构分离和组合扩展。

组合模式

强调“整体-部分”关系,桥接是抽象-实现的解耦。

4.4. 🧠 桥接模式场景总结

项目

桥接模式适用

不适用

是否有多维度扩展需求

✅ 是

❌ 否

抽象与实现是否可独立变化

✅ 是

❌ 固定结构

是否需要运行时切换实现

✅ 是

❌ 编译时固定

是否容易产生类爆炸

✅ 是

❌ 单维度扩展

是否需要组合替代继承

✅ 是

❌ 简单结构即可

性能是否极端敏感

❌ 可能不是最佳方案

✅ 低层性能场景

5. 桥接设计模式实战示例

下面是一个完整的、基于 Spring 的风控系统桥接模式示例,增加了策略注册表 + 配置驱动的动态组合能力。适用于金融风控系统中,针对不同风控维度、选择不同的数据源策略执行风控校验。

5.1. 使用桥接模式的意图

  • 抽象部分:风控维度类型(维度逻辑)
  • 实现部分:数据源(策略实现来源)
  • 风控维度:identityCheckfraudCheck 等。
  • 数据源:localSourcethirdPartySource 等。
  • 维度 + 数据源 = 组合执行。
  • 动态组合来源于配置,例如:
risk:dimension-config:identityCheck: localSourcefraudCheck: thirdPartySource

5.2. ✅ 项目结构概览

risk/
├── dimension/          # 风控维度定义(Abstraction)
│   ├── RiskDimension.java
│   ├── IdentityCheck.java
│   └── FraudCheck.java
├── datasource/         # 数据源定义(Implementor)
│   ├── DataSourceStrategy.java
│   ├── LocalRuleSource.java
│   └── ThirdPartySource.java
├── registry/           # 策略注册表
│   ├── DimensionRegistry.java
│   └── RiskProperties.java
└── controller/         # Controller 调用入口└── RiskController.java

5.3. 🧱 数据源接口与实现(Implementor)


public interface DataSourceStrategy {Map<String, Object> fetchData(String userId);
}
@Component("localSource")
public class LocalRuleSource implements DataSourceStrategy {public Map<String, Object> fetchData(String userId) {return Map.of("identityScore", 90, "fraudRiskLevel", "LOW");}
}@Component("thirdPartySource")
public class ThirdPartySource implements DataSourceStrategy {public Map<String, Object> fetchData(String userId) {return Map.of("identityScore", 80, "fraudRiskLevel", "HIGH");}
}

5.4. 🧱 维度抽象(Abstraction)

public abstract class RiskDimension {protected final DataSourceStrategy dataSource;public RiskDimension(DataSourceStrategy dataSource) {this.dataSource = dataSource;}public abstract boolean check(String userId);
}
@Component("identityCheck")
public class IdentityCheck extends RiskDimension {public IdentityCheck(@Lazy DataSourceStrategy dataSource) {super(dataSource);}@Overridepublic boolean check(String userId) {int score = (int) dataSource.fetchData(userId).get("identityScore");return score >= 85;}
}
@Component("fraudCheck")
public class FraudCheck extends RiskDimension {public FraudCheck(@Lazy DataSourceStrategy dataSource) {super(dataSource);}@Overridepublic boolean check(String userId) {String risk = (String) dataSource.fetchData(userId).get("fraudRiskLevel");return !"HIGH".equalsIgnoreCase(risk);}
}

⚠️ 注意:这里 @Component + @Lazy 仅示意,最终我们会用工厂+注册表生成具体实例。

5.5. 🧱 配置类

5.5.1. application.yml

risk:dimension-config:identityCheck: localSourcefraudCheck: thirdPartySource

5.5.2. RiskProperties.java

@Configuration
@ConfigurationProperties(prefix = "risk")
public class RiskProperties {private Map<String, String> dimensionConfig = new HashMap<>();public Map<String, String> getDimensionConfig() {return dimensionConfig;}public void setDimensionConfig(Map<String, String> dimensionConfig) {this.dimensionConfig = dimensionConfig;}
}

5.6. 🧱 注册表 + 工厂

@Component
public class DimensionRegistry {private final Map<String, Function<DataSourceStrategy, RiskDimension>> dimensionFactories = new HashMap<>();private final Map<String, DataSourceStrategy> dataSources;private final RiskProperties properties;public DimensionRegistry(List<DataSourceStrategy> dataSourceList,RiskProperties properties) {this.dataSources = dataSourceList.stream().collect(Collectors.toMap(bean -> bean.getClass().getAnnotation(Component.class).value(), bean -> bean));this.properties = properties;// 注册维度逻辑工厂(风控维度 -> 构造函数)dimensionFactories.put("identityCheck", IdentityCheck::new);dimensionFactories.put("fraudCheck", FraudCheck::new);}public List<RiskDimension> buildRiskDimensions() {List<RiskDimension> dimensions = new ArrayList<>();for (Map.Entry<String, String> entry : properties.getDimensionConfig().entrySet()) {String dimensionKey = entry.getKey();String dataSourceKey = entry.getValue();Function<DataSourceStrategy, RiskDimension> factory = dimensionFactories.get(dimensionKey);DataSourceStrategy dataSource = dataSources.get(dataSourceKey);if (factory != null && dataSource != null) {dimensions.add(factory.apply(dataSource));}}return dimensions;}
}

5.7. 🧱 Controller 示例

@RestController
@RequestMapping("/risk")
public class RiskController {private final DimensionRegistry registry;public RiskController(DimensionRegistry registry) {this.registry = registry;}@GetMapping("/check")public String check(@RequestParam String userId) {List<RiskDimension> dimensions = registry.buildRiskDimensions();for (RiskDimension dimension : dimensions) {boolean pass = dimension.check(userId);if (!pass) {return "风控未通过: " + dimension.getClass().getSimpleName();}}return "全部风控校验通过 ✅";}
}

5.8. ✅ 示例访问

GET /risk/check?userId=10001

5.9. ✅ 总结亮点

特性

实现方式

桥接模式

RiskDimension 与 DataSourceStrategy 分离

动态组合

YAML + DimensionRegistry 注册表

便于扩展

新增维度 / 数据源只需注册工厂或添加配置

对象交给 Spring 管理

@Component 自动注入、收集策略

6. 桥接设计模式思考

6.1. spring多种注册方式理解

@Component("urgent")
public class UrgentMessageStrategy implements MessageStrategy {private final MessageSender sender;public UrgentMessageStrategy(@Qualifier("emailSender") MessageSender sender) {this.sender = sender;}@Overridepublic void send(String content, String toUser) {new Message(sender) {@Overridepublic void send(String content, String toUser) {sender.send("[加急] " + content, toUser);}}.send(content, toUser);}
}

你这个 UrgentMessageStrategy 完全可以使用 自动注入的方式,让 Spring 管理和注入所需的 MessageSender 实例,下面是你当前代码的简化版以及替代方案:

6.1.1. 当前写法(构造方法注入,使用 @Qualifier 指定具体实现)

这是推荐方式,优点是:

  • 构造注入更清晰,有利于单元测试。
  • @Qualifier 显式声明依赖于哪个 Bean,清楚、稳定

6.1.2. ✅ 替代方式 1:字段注入 + @Qualifier

@Component("urgent")
public class UrgentMessageStrategy implements MessageStrategy {@Autowired@Qualifier("emailSender")private MessageSender sender;@Overridepublic void send(String content, String toUser) {new Message(sender) {@Overridepublic void send(String content, String toUser) {sender.send("[加急] " + content, toUser);}}.send(content, toUser);}
}

字段注入也能用,但不推荐在实际项目中使用(测试不友好,易失控)。

6.1.3. ✅ 替代方式 2:@Resource(name = "emailSender")(使用 JSR-250 规范)

@Component("urgent")
public class UrgentMessageStrategy implements MessageStrategy {@Resource(name = "emailSender")private MessageSender sender;@Overridepublic void send(String content, String toUser) {new Message(sender) {@Overridepublic void send(String content, String toUser) {sender.send("[加急] " + content, toUser);}}.send(content, toUser);}
}

这也是一种替代方式,但语义上不如 @Qualifier 灵活。

6.1.4. 🚫 不建议的方式:使用 @Autowired 而不指定 @Qualifier

@Autowired
private MessageSender sender;

这种写法只有在系统中只有一个 MessageSender 实现时才不会报错。一旦有多个(如 smsSenderemailSenderdingSender),Spring 会抛出 NoUniqueBeanDefinitionException 异常。

6.1.5. ✅ spring对象注入总结

注入方式

是否可行

推荐度

说明

构造注入 + @Qualifier

⭐⭐⭐⭐⭐

推荐方式,稳定、测试友好

字段注入 + @Qualifier

⭐⭐

可用,但不推荐生产使用

@Resource(name = "...")

⭐⭐⭐

简洁但较老,语义较弱

@Autowired@Qualifier

❌ 多实现会报错

不推荐

博文参考

  • 桥接设计模式
  • 2. 桥接模式 — Graphic Design Patterns

http://www.hkcw.cn/article/AbcjiUjTHg.shtml

相关文章

java反射

简介 获取Class 误区 解释一下 “类” 和 “Class对象” 的区别&#xff0c;以及为什么每个类都有关联的 Class 对象&#xff1a; &#x1f9e9; 核心概念&#xff1a;类 vs Class对象 想象你有一本《汽车使用说明书》&#xff1a; 类 这本说明书本身&#xff08;纸上的文…

C++ 之 多态 【虚函数表、多态的原理、动态绑定与静态绑定】

目录 前言 1.多态的原理 1.1虚函数表 1.2派生类中的虚表 1.3虚函数、虚表存放位置 1.4多态的原理 1.5多态条件的思考 2.动态绑定与静态绑定 3.单继承和虚继承中的虚函数表 3.1单继承中的虚函数表 3.2多继承(非菱形继承)中的虚函数表 4.问答题 前言 需要声明的&#x…

28 C 语言作用域详解:作用域特性(全局、局部、块级)、应用场景、注意事项

1 作用域简介 作用域定义了代码中标识符&#xff08;如变量、常量、数组、函数等&#xff09;的可见性与可访问范围&#xff0c;即标识符在程序的哪些位置能够被引用或访问。在 C 语言中&#xff0c;作用域主要分为三类&#xff1a; 全局作用域局部作用域块级作用域 需注意&am…

day03-Vue-Element

1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xf…

智慧交通设计方案

该文档是智慧交通设计方案,交通设计位于综合交通规划后、道路工程设计前,目标是优化交通系统及设施,实现交通安全、高效、可持续发展。内容涵盖区域交通组织优化(含需求管理、速度管理等)、平面交叉口设计(要素、改善措施)、专项交通设计(公共交通、慢行系统等)、智能…

SAP学习笔记 - 开发17 - 前端Fiori开发 Component 配置(组件化)

上一章讲了Fiori前端开发中的国际化。 SAP学习笔记 - 开发16 - 前端Fiori开发 Properties文件&#xff08;国际化&#xff09; &#xff0c;语言切换实例&#xff0c;Fiori 国际化&#xff08;常用语言列表&#xff0c;关键规则&#xff0c;注意事项&#xff09;-CSDN博客 本…

leetcode刷题日记——二叉树的层平均值

[ 题目描述 ]&#xff1a; [ 思路 ]&#xff1a; BFS&#xff0c;通过层次遍历求得每层的和&#xff0c;然后取平均数&#xff0c;存入结果数组树中节点个数在1-10000之间&#xff0c;那么结果数组最大为10000个结果&#xff0c;层数最多为 2n-1>10000&#xff0c;可以推…

Google Android 14设备和应用通知 受限制的设置 出于安全考虑......

重要提示&#xff1a; 文中部分步骤仅适用于 Android 13 及更高版本。了解如何查看 Android 版本。 启用受限制的设置后&#xff0c;应用将能够访问敏感信息&#xff0c;而这可能使您的个人数据面临风险。除非您信任该应用的开发者&#xff0c;否则我们不建议您允许访问受限制…

【小米拥抱AI】小米开源视觉大模型—— MiMo-VL

MiMo-VL-7B模型的开发包含两个序贯训练过程&#xff1a;&#xff08;1&#xff09;四阶段预训练&#xff0c;涵盖投影器预热、视觉-语言对齐、通用多模态预训练及长上下文监督微调&#xff08;SFT&#xff09;&#xff0c;最终生成MiMo-VL-7B-SFT模型&#xff1b;&#xff08;2…

自编码器Auto-encoder(李宏毅)

目录 编码器的概念&#xff1a; 为什么需要编码器&#xff1f; 编码器什么原理&#xff1f; 去噪自编码器: 自编码器的应用&#xff1a; 特征解耦 离散隐表征 编码器的概念&#xff1a; 重构&#xff1a;输入一张图片&#xff0c;通过编码器转化成向量&#xff0c;要求再…

Claude 4 升级:从问答助手到任务执行者 | AI大咖说

Claude 4 升级&#xff1a;从问答助手到任务执行者 Claude 4 升级历程 2025-05-22日&#xff0c;Anthropic 正式发布了他们的新 AI 模型 Claude 4。这标志着 AI 不再仅仅是一个智能问答系统&#xff0c;而是开始具备独立完成复杂任务的能力。CEO Dario Amodei 在发布会中强调…

Day42 Python打卡训练营

知识点回顾 1.回调函数 2.lambda函数 3.hook函数的模块钩子和张量钩子 4.Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 1.回调函数 Hook本质是回调函数&#xff0c;所以我们先介绍一下回调函数 回调函数是作为参数传递给其他函数的函数&#xff0c;其目的是在某个特…

2002-2022年 城市市政公用设施水平、环境、绿地等数据-社科经管实证数据

2002-2022年城市市政公用设施水平、环境、绿地等数据-社科经管https://download.csdn.net/download/paofuluolijiang/90880456 https://download.csdn.net/download/paofuluolijiang/90880456 《2002-2022年城市市政公用设水平、环境、绿地等数据-社科经管实证数据》整理自多源…

uni-app学习笔记十七-css和scss的使用

SCSS 和 CSS的异同点 我们可以使用css和scss来设置样式。其中SCSS&#xff08;Sassy CSS&#xff09;是 CSS 预处理器 Sass&#xff08;Syntactically Awesome Stylesheets&#xff09;的一种语法格式&#xff0c;而 CSS&#xff08;Cascading Style Sheets&#xff09;是标准…

达梦分布式集群DPC_分布式事务理解_yxy

达梦分布式集群DPC_分布式事务理解 1 分布式事务是什么&#xff1f;2 分布式事务怎么实现&#xff1f;2.1 两阶段提交保障一致性2.1.1 预提交2.1.2 提交 2.2 RAFT协议保障数据强一致2.3 全局事务管理2.3.1 全局事务信息的登记流程2.3.2 数据可见性判断规则 1 分布式事务是什么&…

性能优化 - 案例篇:缓冲区

文章目录 Pre1. 引言2. 缓冲概念与类比3. Java I/O 中的缓冲实现3.1 FileReader vs BufferedReader&#xff1a;装饰者模式设计3.2 BufferedInputStream 源码剖析3.2.1 缓冲区大小的权衡与默认值 4. 异步日志中的缓冲&#xff1a;Logback 异步日志原理与配置要点4.1 Logback 异…

【目标检测】检测网络中neck的核心作用

1. neck最主要的作用就是特征融合&#xff0c;融合就是将具有不同大小感受野的特征图进行了耦合&#xff0c;从而增强了特征图的表达能力。 2. neck决定了head的数量&#xff0c;进而潜在决定了不同尺度样本如何分配到不同的head&#xff0c;这一点可以看做是将整个网络的多尺…

基于机器学习的心脏病预测模型构建与可解释性分析

一、引言 心脏病是威胁人类健康的重要疾病之一&#xff0c;早期预测和诊断对防治心脏病具有重要意义。本文利用公开的心脏病数据集&#xff0c;通过机器学习算法构建预测模型&#xff0c;并使用 SHAP 值进行模型可解释性分析&#xff0c;旨在为心脏病的辅助诊断提供参考。 二、…

每日算法-250601

每日算法 - 250601 记录今天完成的算法题目。 1. 1749. 任意子数组和的绝对值的最大值 题目描述 思路 前缀和 解题过程 子数组的和 sum(nums[i..j]) 可以通过前缀和 prefixSum[j] - prefixSum[i-1] 来计算&#xff08;规定 prefixSum[-1] 0&#xff09;。 我们要求的是 ab…