前言
桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
桥接模式
桥接模式(bridge pattern)的定义是:将抽象部分与它的实现部分分离,使它们都可以各自进行独立地变化。
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,将类之间的静态继承关系转变为动态的组合关系,使得系统更加灵活,并易于扩展。而且有效的控制了系统中类的个数 (避免了继承层次的指数级爆炸)。
桥接模式结构
桥接模式原理
桥接模式桥接(Bridge)原理的核心首先有要识别出一个类所具有的的两个独立变化维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合。总结一句话就是:抽象角色引用实现角色
桥接模式包含以下主要角色:
- 抽象化(Abstraction)角色 :主要负责定义出角色的行为,并包含一个对实现化对象的引用。
- 扩展抽象化(RefinedAbstraction)角色 :是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
- 实现化(Implementor)角色 :定义实现化角色的接口,包含角色必须的行为和属性,并供扩展抽象化角色调用。
- 具体实现化(Concrete Implementor)角色 :给出实现化角色接口的具体实现。
例如我们还是拿汽车举例,型号跟颜色相对来说的两个维度,如下:
- 型号一般都是某个汽车固定的几个,所以可以抽象出一个汽车类,将各种型号的汽车类作为其子类,如下图右方。
- 而颜色是汽车的另外的维度,它与汽车之间存在一种设置的关系,因此可以提供一个抽象的颜色接口,将具体颜色作为该接口的子类。
桥接模式的应用示例
我们在日常中都离不开支付,我们来模拟不同的支付工具对应不同的支付模式,比如微信和支付宝都可以完成支付操作,而支付操作又可以有扫码支付、密码支付、人脸支付等,那么关于支付操作其实就有两个维度,支付渠道和支付方式。
原始方式
虽然是满足了功能,但是如果代码设计是这样的,后续的维护和扩展都将会变得非常复杂。
/**
*
* @author Duansg
* @date 2022-12-19 12:25 上午
*/
@Slf4j
public class PayService {
private static Integer ALIPAY = 1;
private static Integer WECHATPAY = 2;
/**
* 支付
*
* @param userId 用户id
* @param traceId 流水号
* @param amount 金额
* @param channelType 渠道类型[1:支付宝,2:微信]
* @param modeType 支付模式[1:密码,2:人脸,3:指纹]
* @return
*/
public Boolean doPay(Long userId, String traceId, BigDecimal amount, Integer channelType, Integer modeType) {
if (ALIPAY.equals(channelType)) {
log.info("支付宝支付进行中......");
if (1 == modeType) {
log.info("密码支付");
// 密码支付安全校验.
// 支付
} else if (2 == modeType) {
log.info("人脸支付");
// 人脸支付安全校验.
// 支付
} else if (3 == modeType) {
log.info("指纹支付");
// 指纹支付安全校验.
// 支付
}
}
if (WECHATPAY.equals(channelType)) {
log.info("微信支付进行中......");
if (1 == modeType) {
log.info("密码支付");
// 密码支付安全校验.
// 支付
} else if (2 == modeType) {
log.info("人脸支付");
// 人脸支付安全校验.
// 支付
} else if (3 == modeType) {
log.info("指纹支付");
// 指纹支付安全校验.
// 支付
}
}
return Boolean.TRUE;
}
}
桥接模式
桥接模式核心原理是首先有要识别出一个类所具有的的两个独立变化维度,将它们设计为两个独立的继承等级结构,为两个维度都提供抽象层,并建立抽象耦合,如下所示:
实现化角色
/**
* @author Duansg
* @date 2022-12-19 12:58 上午
*/
public interface PayMode {
/**
* 安全校验
*
* @param userId 用户ID
* @return
*/
boolean security(Long userId);
}
具体实现化角色
/**
* @author Duansg
* @date 2022-12-19 1:05 上午
*/
@Slf4j
public class PayCypher implements PayMode {
@Override
public boolean security(Long userId) {
log.info("密码支付安全校验.");
return false;
}
}
/**
* @author Duansg
* @date 2022-12-19 1:05 上午
*/
@Slf4j
public class PayFaceMode implements PayMode {
@Override
public boolean security(Long userId) {
log.info("人脸支付安全校验.");
return false;
}
}
/**
* @author Duansg
* @date 2022-12-19 1:04 上午
*/
@Slf4j
public class PayFingerprintMode implements PayMode {
@Override
public boolean security(Long userId) {
log.info("指纹支付安全校验.");
return false;
}
}
抽象化角色
/**
* @author Duansg
* @date 2022-12-19 1:07 上午
*/
public abstract class Pay {
/**
* 桥接支付方式对象
*/
protected PayMode payMode;
public Pay(PayMode payMode) {
this.payMode = payMode;
}
/**
* 支付
*
* @param userId 用户id
* @param traceId 流水号
* @param amount 金额
* @return
*/
public abstract String doPay(Long userId, String traceId, BigDecimal amount);
}
扩展抽象化角色
/**
* @author Duansg
* @date 2022-12-19 1:09 上午
*/
public class Alipay extends Pay {
public Alipay(PayMode payMode) {
super(payMode);
}
@Override
public String doPay(Long userId, String traceId, BigDecimal amount) {
if(!payMode.security(userId)){
return "failed";
}
// 支付.....
return "success";
}
}
/**
* @author Duansg
* @date 2022-12-19 1:10 上午
*/
public class WechatPay extends Pay {
public WechatPay(PayMode payMode) {
super(payMode);
}
@Override
public String doPay(Long userId, String traceId, BigDecimal amount) {
if(!payMode.security(userId)){
return "failed";
}
// 支付.....
return "success";
}
}
Test Unit
/**
* @author Duansg
* @date 2022-12-19 1:12 上午
*/
public class TestUnit {
public static void main(String[] args) {
Pay wechatPay = new WechatPay(new PayFaceMode());
wechatPay.doPay(100001L, "ASDFGHJ", new BigDecimal(100));
Pay alipay = new Alipay(new PayFingerprintMode());
alipay.doPay(100001L, "ERTYUI1", new BigDecimal(200));
}
}
不难看出,在桥接模式上结构更加清晰整洁, 可读性和易用性更高,外部的使用接口的用户不需要关心具体实现。桥接模式满足了单一职责原则和开闭原则,让每一部分都更加清晰并且易扩展。
桥接模式使用场景
- 需要提供平台独立性的应用程序时。 比如,不同数据库的JDBC驱动程序等。
- 需要在某种统一协议下增加更多组件时。 比如,在支付场景中,我们期望支持微信、支付宝、各大银行的支付组件等。这里的统一协议是收款、支付、扣款,而组件就是微信、支付宝等。
- 基于消息驱动的场景。 虽然消息的行为比较统一,主要包括发送、接收、处理和回执,但其实具体客户端的实现通常却各不相同,比如,手机短信、邮件消息、QQ 消息、微信消息等。
- 拆分复杂的类对象时。 当一个类中包含大量对象和方法时,既不方便阅读,也不方便修改。
- 希望从多个独立维度上扩展时。 比如,系统功能性和非功能性角度,业务或技术角度等。
总结
优点:
- 分离抽象接口及其实现部分,桥接模式使用"对象间的关联关系"解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。
- 在很多情况下,桥接模式可以取代多层继承方案,多层继承方案违背了单一职责原则,复用性差。类的个数多,桥接模式很好的解决了这些问题。
- 桥接模式提高了系统的扩展性,在两个变化维度中任意扩展一个维度都不需要修改原有系统,符合开闭原则。
缺点:
- 桥接模式的使用会增加系统的理解和设计难度,由于关联关系建立在抽象层,要求开发者一开始就要对抽象层进行设计和编程。
- 桥接模式要求正确识别出系统中的两个独立变化的维度,因此具有一定的局限性,并且如果正确的进行维度的划分,也需要相当丰富的经验。