事实上,在系统性地学习23种设计模式之前,我从未听说过桥接模式。
现实也是如此,技术有太多太多,能够解决各种各样的问题,但是实际上平常开发需要用到的,或许不到十之五六,一些“旁门左道”甚至你连听都没有听说过,当然这里的“旁门左道”不是贬义词,因为我加了引号——技术人的严谨和求生欲。
然而就是这些你连听都没有听说过的技术,可能在日常工作中已经被你无意识地应用了,桥接模式或许就是此中翘楚。所以说,能够将这些概念总结归纳并提炼出来的人,可真是牛啊。
1. 定义
桥接模式是一种结构型设计模式,与代理模式的“为其他对象提供一种代理以控制对这个对象的访问”不同的是,桥接模式的定义是:“将抽象和实现解耦,让它们可以独立变化。”
桥接模式另外还有一种理解:“通过组合代替继承,让不同纬度的抽象可以独立变化。”
可能这两种理解从字面上都比较抽象,但事实上我们在日常开发中已经运用的比较多了。
例如“通过组合代替继承”,我们在企业级分层思想驱动的开发过程中,一般有几种分层:
- Dao,数据持久层,一些通用的 CRUD,一般以持久化模型为单位
- Service,服务层,组合一个或多个 Dao 实例,实现跨表的通用业务
- Controller,视图层,组合一个或多个 Service 实例,实现跨多业务的功能
这里在 Service 和 Controller 层就运用到了桥接模式的思想。就拿 Controller 来说吧,我们通常会在一个 Controller 中注入多个 Service 来完成不同业务的编排与融合的功能,这就是一种组合思想。而 Service 我们一般声明为接口,因为它可能有多种实现,如同步与异步的多种实现、不同硬件平台的不同实现、基于不同入参的多种实现等。对于 Controller 来说,它只关注 Service 提供的能力就够了,与具体实现逻辑无关。
这是不是跟面向接口编程的思想非常像?所以说技术都是相通的。
2. 类图
在桥接模式中,有这样几种角色:
- 实现角色:定义角色必需的行为和属性,一般是接口或抽象类
- 具体实现角色:实现角色的具体实现类
- 抽象角色:定义角色的行为,同时组合一个实现角色,该角色一般是抽象类或接口
- 修正抽象角色:覆盖抽象角色对实现角色的引用及调用,该角色可以不存在
3. 示例
就拿上文中说到的同步与异步实现的场景来举一个例子,目前的场景是:用户注册后,营销中心将调用积分中心提供的接口对用户发放注册积分奖励。此时,抽象角色是营销中心,实现角色是积分中心,具体实现角色是同步积分中心实现类,异步积分中心实现类。
说一下这里为什么会有同步和异步的实现类:积分中心为了保证发放积分接口的 TPS 能够达到客户的要求,将其改为异步发放,只记录发放动作,通过 MQ 消息异步执行充值动作,同时基于消息重试保证最终一致性。
抽象角色——营销中心,提供注册有礼方法,其实现代码如下:
public class MarketingCenter {
private PointCenter pointCenter;
public MarketingCenter(PointCenter pointCenter) {
this.pointCenter = pointCenter;
}
/**
* 注册有礼
*
* @param userId 用户ID
* @param pointValue 发放积分值
*/
public void RegistrationGift(String userId, Long pointValue) {
System.out.printf("[营销中心]用户[%s]注册成功,发放奖励积分值[%s]\n", userId, pointValue);
pointCenter.grantPoint(userId, pointValue);
}
}
实现角色——积分中心抽象接口,提供发放积分方法,其实现代码如下:
public interface PointCenter {
/**
* 发放积分
*
* @param userId 用户ID
* @param pointValue 发放积分值
*/
void grantPoint(String userId, Long pointValue);
}
具体实现角色——同步/异步实现类代码如下:
public class SyncPointCenter implements PointCenter {
@Override
public void grantPoint(String userId, Long pointValue) {
System.out.printf("[同步积分中心实现类]用户[%s]发放积分值[%s]\n", userId, pointValue);
}
}
public class AsyncPointCenter implements PointCenter {
@Override
public void grantPoint(String userId, Long pointValue) {
System.out.printf("[异步积分中心实现类]用户[%s]发放积分值[%s]\n", userId, pointValue);
}
}
最后编写测试代码:
public class Test {
public static void main(String[] args) {
SyncPointCenter syncPointCenter = new SyncPointCenter();
MarketingCenter marketingCenter = new MarketingCenter(syncPointCenter);
// 注册有礼
marketingCenter.RegistrationGift("1", 100L);
}
}
// output:
// [营销中心]用户[1]注册成功,发放奖励积分值[100]
// [同步积分中心实现类]用户[1]发放积分值[100]
4. 使用场景
桥接模式的使用场景是:
- 不想使用继承的情况:桥接模式是以组合代替继承,当然适用于不想使用继承的情况
- 抽象角色不稳定:这也是不使用继承的原因
- 方便扩展:如果有新的需求,只需要再组合一个新的实现角色即可
- 面向接口编程,不感知具体实现:抽象角色只需要组合实现角色接口/抽象类,不需要感知具体的实现逻辑,这与 Java 虚拟机的实现思想很类似—— JVM 保证了不同操作系统运行一段代码的结果是一致的,即“一次编译,到处运行”
5. 小结
本文讲述了桥接模式,它的定义是——将抽象和实现解耦,让它们可以独立变化。我们通常会以组合的形式来实现桥接模式,这很好地避免了多层继承带来的一系列问题。桥接模式在日常开发中其实十分常用,当然对于其可拓展性的应用,大多数人没有进行很好地实践。
6. 参考资料
- 《设计模式之禅》(第2版)
最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。
文档信息
- 本文作者:Planeswalker23
- 本文链接:https://planeswalker23.github.io/2022/02/01/%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%B3%BB%E5%88%97-%E7%AC%AC6%E7%AF%87-%E6%88%91%E6%B2%A1%E5%90%AC%E8%AF%B4%E8%BF%87%E7%9A%84%E6%A1%A5%E6%8E%A5%E6%A8%A1%E5%BC%8F/
- 版权声明:本作品系原创,作者保留所有权利,未经作者允许,禁止转载和演绎。