设计模式——桥接设计模式(结构型)
摘要
桥接设计模式是一种结构型设计模式,用于将抽象与实现解耦,使二者可以独立变化。它通过将一个类拆分为“抽象”和“实现”两部分,并通过桥接关系组合,避免了类继承层次结构过于庞大。桥接模式包含抽象类、扩充抽象类、实现类接口和具体实现类等角色。在实现方式上,结合了策略模式,适用于风控系统通知等场景。桥接模式适合维度较多的场景,与其他设计模式有明显区别,实战示例展示了其在项目中的应用和优势。
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. 使用桥接模式的意图
- 抽象部分:风控维度类型(维度逻辑)
- 实现部分:数据源(策略实现来源)
- 风控维度:
identityCheck
、fraudCheck
等。 - 数据源:
localSource
、thirdPartySource
等。 - 维度 + 数据源 = 组合执行。
- 动态组合来源于配置,例如:
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 管理 |
|
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
实现时才不会报错。一旦有多个(如 smsSender
、emailSender
、dingSender
),Spring 会抛出 NoUniqueBeanDefinitionException
异常。
6.1.5. ✅ spring对象注入总结
注入方式 | 是否可行 | 推荐度 | 说明 |
构造注入 + | ✅ | ⭐⭐⭐⭐⭐ | 推荐方式,稳定、测试友好 |
字段注入 + | ✅ | ⭐⭐ | 可用,但不推荐生产使用 |
| ✅ | ⭐⭐⭐ | 简洁但较老,语义较弱 |
仅 | ❌ 多实现会报错 | ❌ | 不推荐 |
博文参考
- 桥接设计模式
- 2. 桥接模式 — Graphic Design Patterns