1. 概述
桥接模式是一个非常简单的设计模式,可能大家在开发的过程中已经使用到了这种模式而不自知。总的来说,桥接模式最大的作用就是解耦,所谓的解耦,就是通过转换代码的设计,减少类与类,模块与模块之间的依赖紧密程度。
2.桥接模式
2.1.两种定义
桥接模式的主流定义有两种,第一种是GoF的定义:
将抽象和实现解耦,使得两者可以独立地变化。
通过这句话,我们能够得到的信息是需要降低抽象和实现和依赖紧密程度,以达到两者之间可以独立扩展,互不影响的效果。
在大多数情况下,抽象一般指的是接口和抽象类,实现就是实现类。在桥接模式中的抽象和实现,无所谓接口、抽象类还是实现类的差异。桥接模式抽象更多的是提供一定的约束,比如一个固定的执行流程,在这个流程中会定义出一些可以自由替换的口子,而实现就是去适配这些口子,让一个固定的执行流程可以得到不同的执行结果。在这个定义下,桥接模式中的抽闲与实现,可能各自都是一套类库,或者是一个独立的模块。
简单的理解,就是抽象会定义要做什么,实现是定义怎么去做,也就理解为插口与插件的关系。
比如:操作系统和文件系统的关系、JDBC
中连接与数据库驱动之间的关系、Dubbo
中协议与服务提供/调用者的封装之间的关系,都可以看做是这种抽象与实现分离的桥接关系。
除了上述的框架或开源代码之外,我们在实际的开发过程中也经常会有类似的需求。
比如:系统需要接入支付功能的时候,会先定义一套支付流程,在这个流程的节点中会调用不同的三方支付渠道,微信、支付宝等,这里的支付流程就是抽象,支付渠道就是实现。
再比如:需要实现一个消息发送平台,消息发送的流程就是抽象,不同的消息发送渠道(短信、邮件、推送、站内信等)就是实现,更进一步在短信发送中,短信发送的通用流程就是抽象,调用不同的短信发送渠道商就是实现。
第二种定义方式更为简单使用更加广泛:
一个类存在多个独立变化的维度,通过组合的方式让多个维度可以独立进行扩展。
为什么说这种方式更加广泛呢?
思考一下就会发现,在第一种定义中抽象和实现完全可以理解为两个不同的维度,这两个维度都是可以独立变化的,也就是说,第二种定义包含了第一种定义,并在这个基础上扩展了,即使不满足抽象与实现的关系,只要有多个不同的维护可以独立变化,并且通过组合/聚合
的方式实现不同维度独立扩招,就可以认为是桥接模式。
举个例子:
还是上面说的消息发送的需求,在短信发送中,常规的短信在服务商那里有两种类型:通知类、营销类。
对于营销类型的短信,需要在短信的正文末尾拼接上回T退订
,回TD退订
或者类似的文本,而通知类型的是不需要的,这时候就可以将短信类型作为一个维度,短信渠道作为另一个维度,将两个维度通过组合的方式连接起来,就形成了一个简单的桥接模式实现。
在这个例子中,并没严格的抽象与实现的关系。
2.2.通用类图/通用代码
根据上面的描述,不同维度之间通过组合/聚合的方式进行连接,可以得到如下的类图:
通用代码实现:
- 维度B:
public interface DimensionB { void operation(); } public class DimensionBImpl implements DimensionB { @Override public void operation() { System.out.println("维度B的实现"); } }
- 维度A:
public abstract class DimensionA { private DimensionB dimensionB; public DimensionA(DimensionB dimensionB) { this.dimensionB = dimensionB; } public void operation() { dimensionB.operation(); } } public class DimensionAImpl extends DimensionA { public DimensionAImpl(DimensionB dimensionB) { super(dimensionB); } }
2.3.在业务开发中的应用
参照通用的代码和类图,可以在实际的开发中使用桥接模式,以上面讲的不同短信类型为例,简单的实现一下短信发送的业务。
- 定义发送渠道的维度:
public interface SmsChannel { void send(String message, String phone); } public class AliSmsChannel implements SmsChannel { @Override public void send(String message, String phone) { System.out.println("使用阿里云短信通道发送消息:" + message + ",给:" + phone); } }
- 定义短信类型维度:
public abstract class SmsType { private SmsChannel smsChannel; public SmsType(SmsChannel smsChannel) { this.smsChannel = smsChannel; } public void send(String message, String phone) { smsChannel.send(message, phone); } } public class NotificationSmsType extends SmsType { public NotificationSmsType(SmsChannel smsChannel) { super(smsChannel); } } public class MarketingSmsType extends SmsType { public MarketingSmsType(SmsChannel smsChannel) { super(smsChannel); } @Override public void send(String message, String phone) { message = message + " 回T退订"; super.send(message, phone); } }
- 测试:
public class SmsTest { public static void main(String[] args) { SmsType notificationSmsType = new NotificationSmsType(new AliSmsChannel()); notificationSmsType.send("测试通知类型短信", "18512345678"); System.out.println("====================================="); SmsType marketingSmsType = new MarketingSmsType(new AliSmsChannel()); marketingSmsType.send("测试营销类型短信", "18512345678"); } }
执行测试的结果如下:
使用阿里云短信通道发送消息:测试通知类型短信,给:18512345678
=====================================
使用阿里云短信通道发送消息:测试营销类型短信 回T退订,给:18512345678
此时要拓展一个腾讯云的发送渠道,只需要创建一个新的SmsChannle
,在SmsType
的维度上是不用做修改的,主打的就是不同维度独立拓展。
public class TencentSmsChannel implements SmsChannel {
@Override
public void send(String message, String phone) {
System.out.println("使用腾讯云短信通道发送消息:" + message + ",给:" + phone);
}
}
在做一次测试,对比一下两种渠道:
public class SmsTest {
public static void main(String[] args) {
SmsType notificationSmsType = new NotificationSmsType(new AliSmsChannel());
notificationSmsType.send("测试通知类型短信", "18512345678");
System.out.println("=====================================");
SmsType marketingSmsType = new MarketingSmsType(new AliSmsChannel());
marketingSmsType.send("测试营销类型短信", "18512345678");
System.out.println("=====================================");
MarketingSmsType tencentMarketingSmsType = new MarketingSmsType(new TencentSmsChannel());
tencentMarketingSmsType.send("测试营销类型短信","18512345678");
}
}
使用阿里云短信通道发送消息:测试通知类型短信,给:18512345678
=====================================
使用阿里云短信通道发送消息:测试营销类型短信 回T退订,给:18512345678
=====================================
使用腾讯云短信通道发送消息:测试营销类型短信 回T退订,给:18512345678
3.总结
本篇主要讲了桥接模式的概念及实现,桥接模式本身是一个很简单的设计模式,只需要掌握桥接模式的两个最重要的特点即可:
- 通过组合/聚合进行连接,而不是继承
- 多个维度可以独立拓展,互不影响
这两个特点其实也是对组合优于继承的一种体现,掌握这两个特点,可以在日后的开发过程中识别出桥接模式,并使用桥接模式写出高拓展性的代码。