1. 概述

桥接模式是一个非常简单的设计模式,可能大家在开发的过程中已经使用到了这种模式而不自知。总的来说,桥接模式最大的作用就是解耦,所谓的解耦,就是通过转换代码的设计,减少类与类,模块与模块之间的依赖紧密程度。

2.桥接模式

2.1.两种定义

桥接模式的主流定义有两种,第一种是GoF的定义:

通过这句话,我们能够得到的信息是需要降低抽象实现和依赖紧密程度,以达到两者之间可以独立扩展,互不影响的效果。

在大多数情况下,抽象一般指的是接口和抽象类,实现就是实现类。在桥接模式中的抽象和实现,无所谓接口、抽象类还是实现类的差异。桥接模式抽象更多的是提供一定的约束,比如一个固定的执行流程,在这个流程中会定义出一些可以自由替换的口子,而实现就是去适配这些口子,让一个固定的执行流程可以得到不同的执行结果。在这个定义下,桥接模式中的抽闲实现,可能各自都是一套类库,或者是一个独立的模块。

简单的理解,就是抽象会定义要做什么,实现是定义怎么去做,也就理解为插口与插件的关系。
比如:操作系统文件系统的关系、JDBC中连接与数据库驱动之间的关系、Dubbo协议服务提供/调用者的封装之间的关系,都可以看做是这种抽象与实现分离的桥接关系。

除了上述的框架或开源代码之外,我们在实际的开发过程中也经常会有类似的需求。
比如:系统需要接入支付功能的时候,会先定义一套支付流程,在这个流程的节点中会调用不同的三方支付渠道,微信、支付宝等,这里的支付流程就是抽象,支付渠道就是实现。
再比如:需要实现一个消息发送平台,消息发送的流程就是抽象,不同的消息发送渠道(短信、邮件、推送、站内信等)就是实现,更进一步在短信发送中,短信发送的通用流程就是抽象,调用不同的短信发送渠道商就是实现。


第二种定义方式更为简单使用更加广泛:

为什么说这种方式更加广泛呢?
思考一下就会发现,在第一种定义中抽象和实现完全可以理解为两个不同的维度,这两个维度都是可以独立变化的,也就是说,第二种定义包含了第一种定义,并在这个基础上扩展了,即使不满足抽象与实现的关系,只要有多个不同的维护可以独立变化,并且通过组合/聚合的方式实现不同维度独立扩招,就可以认为是桥接模式。

举个例子:
还是上面说的消息发送的需求,在短信发送中,常规的短信在服务商那里有两种类型:通知类、营销类
对于营销类型的短信,需要在短信的正文末尾拼接上回T退订回TD退订或者类似的文本,而通知类型的是不需要的,这时候就可以将短信类型作为一个维度,短信渠道作为另一个维度,将两个维度通过组合的方式连接起来,就形成了一个简单的桥接模式实现。
在这个例子中,并没严格的抽象与实现的关系。

2.2.通用类图/通用代码

根据上面的描述,不同维度之间通过组合/聚合的方式进行连接,可以得到如下的类图:
【设计模式】桥接模式在开发中的应用-LMLPHP
通用代码实现:

  • 维度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");
        }
    }
    

执行测试的结果如下:


此时要拓展一个腾讯云的发送渠道,只需要创建一个新的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");
    }

}

3.总结

本篇主要讲了桥接模式的概念及实现,桥接模式本身是一个很简单的设计模式,只需要掌握桥接模式的两个最重要的特点即可:

  • 通过组合/聚合进行连接,而不是继承
  • 多个维度可以独立拓展,互不影响

这两个特点其实也是对组合优于继承的一种体现,掌握这两个特点,可以在日后的开发过程中识别出桥接模式,并使用桥接模式写出高拓展性的代码。

09-13 11:49