一、桥接模式介绍
桥接模式(bridge pattern) 的定义是:将抽象部分与它的实现部分分离,使它们都可以独立
地变化。
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,
将类之间的静态继承关系转变为动态的组合关系,使得系统更加灵活,并易于扩展,有效
的控制了系统中类的个数 (避免了继承层次的指数级爆炸)。
二、桥接模式原理
桥接模式类图如下所示:
桥接模式(Bridge)包含以下角色:
1)抽象化(Abstraction)角色 :主要负责定义出该角色的行为 ,并包含一个对实现化
对象的引用。
2)扩展抽象化(RefinedAbstraction)角色 :是抽象化角色的子类,实现父类中的业
务方法,并通过组合关系调用实现化角色中的业务方法。
3)实现化(Implementor)角色 :定义实现化角色的接口,包含角色必须的行为和属
性,并供扩展抽象化角色调用。
4)具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
桥接模式原理的核心是: 首先有要识别出一个类所具有的的两个独立变化维度,将它们设计
为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。
总结一句话就是: 抽象角色引用实现角色。
如我们拿毛笔举例, 型号和颜色是毛笔的两个维度:
1)型号是其固有的维度,所以抽象出一个毛笔类,而将各种型号的毛笔作为其子类,
也就是下图的右侧抽象部分内容。
2)颜色是毛笔的另一个维度,它与毛笔之间存在一种设置的关系,因此可以提供一个
抽象的颜色接口,将具体颜色作为该接口的子类。
三、桥接模式应用示例
以当下支付场景为例,看下不同支付模式中桥接模式的应用,如微信和支付宝都可以完成
支付操作,而支付操作又可以有扫码支付、密码支付、人脸支付等,那么关于支付操作
其实就有两个维度,包括:支付渠道和支付方式,如下图所示:
1、不使用设计模式来模拟实现不同模式的支付场景
/*******************************************************
* 模拟网络支付场景
* 模拟不同的支付工具对应不同的支付模式,比如微信和支付宝都可以完成支付操作,而支付操作又可以有扫码支付、密码支付、人脸支付等,
* 那么关于支付操作其实就有两个维度, 包括:支付渠道和支付方式
*
* 不使用设计模式实现
*
* 不使用设计模式缺点:
* 维护和扩展都会变得非常复杂,需要修改原来代码,风险较大
*
*******************************************************/
public class PayController {
/**
* @param uId 用户id
* @param tradeId 交易流水号
* @param amount 交易金额
* @param channelType 渠道类型 1 微信, 2 支付宝
* @param modeType 支付模式 1 密码,2 人脸,3 指纹
* @return: boolean
*/
public boolean doPay(String uId, String tradeId, BigDecimal amount, int channelType, int modeType){
//微信支付
if(1 == channelType){
System.out.println("微信渠道支付划账开始......");
if(1 == modeType){
System.out.println("密码支付");
}if(2 == modeType){
System.out.println("人脸支付");
}if(3 == modeType){
System.out.println("指纹支付");
}
}
//支付宝支付
if(2 == channelType){
System.out.println("支付宝渠道支付划账开始......");
if(1 == modeType){
System.out.println("密码支付");
}if(2 == modeType){
System.out.println("人脸支付");
}if(3 == modeType){
System.out.println("指纹支付");
}
}
return true;
}
}
//测试
public class Test {
public static void main(String[] args) {
PayController payController = new PayController();
System.out.println("测试: 微信支付、人脸支付方式");
payController.doPay("weixin_001","1000112333333",new BigDecimal(100),1,2);
System.out.println("\n测试: 支付宝支付、指纹支付方式");
payController.doPay("hifubao_002","1000112334567",new BigDecimal(100),2,3);
}
}
虽然不使用设计模式也能实现该支付场景需求,但以后若增加支付渠道或修改支付方式,则
成本比较高,不利于后边的扩展和维护。
2、使用桥接模式重构支付场景代码
我们知道桥接模式原理的核心是: 首先有要识别出一个类所具有的的两个独立变化维度,
将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。
针对该支付场景上边已经抽出了2个维度,即:支付渠道 和 支付方式;这里我们可以把支付
渠道作为抽象化角色,支付方式作为实现化角色,支付渠道*支付模式 = 相对应的支付组合;
这样就得到下边2个类:
1)Pay抽象类(支付渠道)
I)支付渠道子类: 微信支付
II)支付渠道子类: 支付宝支付
2)IPayMode接口(支付方式)
I)支付模式实现: 刷脸支付
II)支付模式实现: 指纹支付
III)密码支付
类图如下:
示例代码如下:
1)实现化角色
/**
* 支付模式接口
* 实现化(Implementor)角色
*/
public interface IPayMode {
//安全校验功能: 对各种支付模式进行风控校验
boolean security(String uId);
}
/*******************************************************
* 密码支付及风控校验
* 具体实现化(Concrete Implementor)角色
*
*******************************************************/
public class PayCypher implements IPayMode{
@Override
public boolean security(String uId) {
return false;
}
}
/*******************************************************
* 刷脸支付及风控校验
* 具体实现化(Concrete Implementor)角色
*
*******************************************************/
public class PayFaceMode implements IPayMode{
@Override
public boolean security(String uId) {
return true;
}
}
/*******************************************************
* 指纹支付及风控校验
* 具体实现化(Concrete Implementor)角色
*
*******************************************************/
public class PayFingerprintMode implements IPayMode{
@Override
public boolean security(String uId) {
return false;
}
}
2)抽象化角色代码
/*******************************************************
* 支付抽象化类
* 抽象化(Abstraction)角色
*
*******************************************************/
public abstract class Pay {
protected IPayMode payMode;
/**
* todo 注意:
* 抽象类的公共构造函数,不能用来new 创建对象(抽象类不能实例化),
* 该构造函数必须由子类调用(在子类构造函数中通过super调用)
*
* @param payMode
*/
public Pay(IPayMode payMode){
this.payMode = payMode;
}
//划账功能
public abstract String transfer(String uId, String tradeId, BigDecimal amount);
}
/*******************************************************
* 支付渠道-微信
* 扩展抽象化(RefinedAbstraction)角色
*
*******************************************************/
public class WxPay extends Pay{
public WxPay(IPayMode payMode) {
super(payMode);
}
@Override
public String transfer(String uId, String tradeId, BigDecimal amount) {
System.out.println("微信渠道支付划账开始......");
//支付方式校验
boolean security = payMode.security(uId);
System.out.println("微信渠道支付风险校验: " + uId + " , " + tradeId +" , " + security);
if(!security){
System.out.println("微信渠道支付划账失败!");
return "500";
}
System.out.println("微信渠道划账成功! 金额: "+ amount);
return "200";
}
}
/*******************************************************
* 支付渠道--支付宝
* 扩展抽象化(RefinedAbstraction)角色
*
*******************************************************/
public class ZfbPay extends Pay{
public ZfbPay(IPayMode payMode) {
super(payMode);
}
@Override
public String transfer(String uId, String tradeId, BigDecimal amount) {
System.out.println("支付宝渠道支付划账开始......");
//支付方式校验
boolean security = payMode.security(uId);
System.out.println("支付宝渠道支付风险校验: " + uId + " , " + tradeId +" , " + security);
if(!security){
System.out.println("支付宝渠道支付划账失败!");
return "500";
}
System.out.println("支付宝渠道划账成功! 金额: "+ amount);
return "200";
}
}
3)测试
public class Test {
public static void main(String[] args) {
System.out.println("测试场景1: 微信支付、人脸方式.");
Pay wxpay = new WxPay(new PayFaceMode());
wxpay.transfer("wx_00100100","10001900",new BigDecimal(100));
System.out.println();
System.out.println("测试场景2: 支付宝支付、指纹方式");
Pay zfbPay = new ZfbPay(new PayFingerprintMode());
zfbPay.transfer("jlu1234567","567689999999",new BigDecimal(200));
}
}
四、桥接模式总结
1、桥接模式优点
1)分离抽象接口及其实现部分,桥接模式使用"对象间的关联关系"解耦了抽象和实现之间
固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化
2)在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了单一职责原则,
复用性差,类的个数多,桥接模式很好的解决了这些问题
3)桥接模式提高了系统的扩展性,在两个变化维度中任意扩展一个维度都不需要修改原
有系统,符合开闭原则
2、桥接模式缺点
1)桥接模式的使用会增加系统的理解和设计难度,由于关联关系建立在抽象层,要求开
发者一开始就要对抽象层进行设计和编程
2)桥接模式要求正确识别出系统中的两个独立变化的维度,因此具有一定的局限性,并且
如果正确的进行维度的划分,也需要相当丰富的经验
3、桥接模式使用场景
1)需要提供平台独立性的应用程序时。 比如,不同数据库的 JDBC 驱动程序、硬盘驱动
程序等。
2)需要在某种统一协议下增加更多组件时。 比如,在支付场景中,我们期望支持微信、
支付宝、各大银行的支付组件等。这里的统一协议是收款、支付、扣款,而组件就是微
信、支付宝等
3)基于消息驱动的场景。 虽然消息的行为比较统一,主要包括发送、接收、处理和回执,
但其实具体客户端的实现通常却各不相同,比如,手机短信、邮件消息、QQ 消息、
微信消息等。
4)拆分复杂的类对象时。 当一个类中包含大量对象和方法时,既不方便阅读,也不方便修改
5)希望从多个独立维度上扩展时。 比如,系统功能性和非功能性角度,业务或技术角度等。