桥接模式将抽象部分与实现部分分离,使它们都可以独立地变化,他也是结构型模式的一种。
桥接模式让实体类接入桥接的接口,使得实体类的功能独立于桥接接口实现类,这两种类型的类可被结构化改变而互不影响。像手机品牌与手机软件的关系,软件独立运行在操作系统上,而跟手机品牌是没有直接关系的,操作系统就像桥梁一样,只要手机品牌用的是同一个操作系统,即使新出一个手机品牌,手机软件依然能够在该品牌上运行。
我们以支付场景为案例,来说明桥接模式的使用。我们支付时通常需要判断支付渠道是什么,支付方式是什么,如果按一般思路编写代码,那就是这个样子的:
public void doPay(String uid, String orderId, String channel, String payType) {
if (channel.equals("wxpay")) {
if (payType.equals("qr")) {
log.info("处理微信二维码支付");
}
if (payType.equals("face")) {
log.info("处理微信刷脸支付");
}
} else if (channel.equals("alipay")) {
if (payType.equals("qr")) {
log.info("处理支付宝二维码支付");
}
if (payType.equals("face")) {
log.info("处理支付宝刷脸支付");
}
}
}
可以看到这种代码,非常不利于维护,如果新增渠道或者方式,更改起来就是灾难。
其实,支付渠道在实际开发中,我们一般使用策略模式去实现的,这个我们后面的篇章会讲到,这里我们只关心支付方式的代码优化。
每一个支付渠道会存在不一样的支付方式,每一种支付方式对参数的校验或对用户的风控又是不一样的,某种支付方式在不同渠道下的逻辑却是一致的。所以这里,我们可以将支付方式抽象化成桥接接口,在支付渠道中进行桥接,支付时传入对应的方式即可。
先将支付渠道抽象化,建立支付渠道接口和支付宝+微信实现类
public interface IPay {
boolean pay(String uid, String orderId);
}
@Slf4j
public class WxPay implements IPay {
@Override
public boolean pay(String uid, String orderId) {
log.info("微信支付");
return true;
}
}
@Slf4j
public class AliPay implements IPay{
@Override
public boolean pay(String uid, String orderId) {
log.info("支付宝支付");
return true;
}
}
再建立支付方式的桥接接口,有二维码支付和刷脸支付两种支付方式的实现
public interface IPayType {
/**
* 用户风控
*/
boolean security(String uid);
}
@Slf4j
public class QRPayType implements IPayType{
@Override
public boolean security(String uid) {
log.info("用户:{}执行二维码风控逻辑,风控结果放行。", uid);
return true;
}
}
@Slf4j
public class FacePayType implements IPayType{
@Override
public boolean security(String uid) {
log.info("用户:{}执行刷脸支付风控逻辑,风控结果放行。", uid);
return true;
}
}
目前可以看到,我们的支付渠道和支付方式是各自隔离的并互不影响的,我们需要在支付渠道上新增抽象类,集成支付方式作为桥接,微信和支付宝实现类需要变更
public abstract class AbstractPay implements IPay {
protected IPayType payType;
public AbstractPay(IPayType payType) {
this.payType = payType;
}
@Override
public boolean pay(String uid, String orderId) {
if (payType.security(uid)) {
return realPay();
}
return false;
}
public abstract boolean realPay();
}
@Slf4j
public class WxPay extends AbstractPay {
public WxPay(IPayType payType) {
super(payType);
}
@Override
public boolean realPay() {
log.info("微信支付");
return true;
}
}
@Slf4j
public class AliPay extends AbstractPay {
public AliPay(IPayType payType) {
super(payType);
}
@Override
public boolean realPay() {
log.info("支付宝支付");
return true;
}
}
桥接完成,编写测试代码进行测试
public static void main(String[] args) {
IPay wxPay = new WxPay(new QRPayType());
wxPay.pay("10001", "abcd");
IPay aliPay = new AliPay(new FacePayType());
aliPay.pay("10001", "abcd");
}
测试结果正确,此时,我们无论新增多少种支付渠道,或者新增多少种支付方式,我们的渠道和方式是可以随意组合的。
在Spring的JdbcTemplate中,桥接模式也是这么使用的,通过构造器注入DataSource,DataSource依赖的是jdbc的驱动接口,它的实现可以是mysql,也可以是oracle,JdbcTemplate不用关心Datasource是如何实现,它是拿来主义,拿来就能用。
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
public JdbcTemplate(DataSource dataSource) {
this.setDataSource(dataSource);
this.afterPropertiesSet();
}
@Nullable
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
//这里就是直接拿构造器注入的DataSource对象
Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
Statement stmt = null;
Object var11;
try {
stmt = con.createStatement();
this.applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
this.handleWarnings(stmt);
var11 = result;
} catch (SQLException var9) {
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, this.getDataSource());
con = null;
throw this.translateException("StatementCallback", sql, var9);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, this.getDataSource());
}
return var11;
}
}
桥接模式就跟桥梁一样,将两个职责不一样的类连接在一起,他们具有相关性, 又可以各自工作互不影响,即使需要扩展一个类,也不会影响到另一个类所承载的职责,所以它满足了单一职责原则和开闭原则。