【设计模式】结构型-⑤桥接模式
...大约 6 分钟
【设计模式】结构型-⑤桥接模式
注
学习核心
- 桥接模式核心
- 概念:将抽象部分和实现部分进行分离,将多种可匹配的使用进行组合(核心实现可以理解为A类中含有B类接口,通过构造函数传递B类的实现,这个B类就是设计的桥)
- 组成:
- A-抽象化角色:抽象化基类(引用实现化对象)、扩展抽象化
- B-实现化角色:实现化接口、具体实现化接口
- 应用场景
- 【支付场景】:多种支付(支付宝、微信)和验证(指纹、人脸识别、密码)方式
- 【数据库JDBC&DriverManager】:JDBC连接数据库,通过DriverManager加载指定数据库驱动程序进行桥接,获取相应的连接实例
基本概念
概念说明
桥接模式:将抽象部分和实现部分进行分离,使其可以独立地变化和运行。避免使用继承导致的类爆炸问题,提供了一种更加灵活的扩展方式,降低了抽象和实现的耦合度
场景案例
1.支付场景(多种支付、验证方式组合)
🧨传统实现方式
针对不同的支付渠道、验证方式,将其进行组合构建支付场景,最传统的实现方式就是硬核通过if...else...、switch条件分支语句进行分析
/**
* 支付处理器
* 核心:根据不同的支付渠道、验证模式进行组合,构建支付场景
* 支付渠道 channelType:1-wechatPay、2-aliPay
* 验证模式 modeType:1-密码验证、2-指纹验证、3-人脸识别
*/
public class PayController {
// 模拟支付
public boolean pay(String uid, String traceId, BigDecimal money,int channelType,int modeType){
String payInfo = uid + "," + traceId + "," + money + "," + channelType + "," + modeType;
// 根据不同的支付渠道、验证模式进行组合,构建支付场景
if(1==channelType){
// 微信支付处理
System.out.println("wechat pay");
if(1==modeType){
System.out.println("wechat 密码验证");
}else if(2==modeType){
System.out.println("wechat 指纹验证");
}else if(3==modeType){
System.out.println("wechat 人脸识别");
}
System.out.println("wechat 支付成功:" + payInfo);
}else if(2==channelType){
// 支付宝支付处理
System.out.println("ali pay");
if(1==modeType){
System.out.println("ali 密码验证");
}else if(2==modeType){
System.out.println("ali 指纹验证");
}else if(3==modeType){
System.out.println("ali 人脸识别");
}
System.out.println("ali 支付成功:" + payInfo);
}
return true;
}
}
当扩展了相应的支付渠道和验证方式,这种传统方式实现到后期会使得代码耦合变得更加臃肿、难扩展
public class PayTest {
public static void main(String[] args) {
PayController payController = new PayController();
// 模拟微信支付
payController.pay("1001","t001",new BigDecimal("100"),1,2);
// 模拟支付宝支付
payController.pay("2001","t0012",new BigDecimal("2000"),2,2);
}
}
-- output
wechat pay
wechat 指纹验证
wechat 支付成功:1001,t001,100,1,2
ali pay
ali 指纹验证
ali 支付成功:2001,t0012,2000,2,2
✨桥接模式优化
分析下现有支付场景,有两种支付渠道,每个支付渠道下都有相应的支付模型(风控验证)。结合桥接模式概念简单理解,A中包括了B类接口,通过构造器传递B类实现,此处的A为支付渠道、B为支付模型,而支付组合即为A×B(支付渠道×支付模型)
- 支付渠道:支付基类(抽象类)、支付实现类(微信支付实现、支付宝支付实现)
- 支付模型:风控模型接口(风控模型接口设计)、风控验证实现类(密码、指纹、人脸识别)
代码实现
- 风控模式设计
/**
* 风控模式
*/
public interface IPayMode {
public void security(String uid);
}
/**
* 风控模式:密码
*/
public class SecretPayMode implements IPayMode {
@Override
public void security(String uid) {
System.out.println("密码验证");
}
}
/**
* 风控模式:指纹验证
*/
public class FingerprintPayMode implements IPayMode {
@Override
public void security(String uid) {
System.out.println("指纹验证");
}
}
/**
* 风控模式:人脸识别
*/
public class FacePayMode implements IPayMode {
@Override
public void security(String uid) {
System.out.println("人脸识别");
}
}
- 支付渠道设计
/**
* 支付基类
*/
public abstract class Pay {
// 定义桥(支付模式)
protected IPayMode payMode;
// 构造函数定义(搭建桥连接)
public Pay(IPayMode payMode) {
this.payMode = payMode;
}
// 支付方法
public abstract void pay(String uid, String traceId, BigDecimal money);
}
/**
* wechat 支付渠道
*/
public class WeChatPay extends Pay{
public WeChatPay(IPayMode payMode) {
super(payMode);
}
@Override
public void pay(String uid, String traceId, BigDecimal money) {
this.payMode.security(uid);
System.out.println("wechat pay");
}
}
/**
* Ali 支付渠道
*/
public class AliPay extends Pay{
public AliPay(IPayMode payMode) {
super(payMode);
}
@Override
public void pay(String uid, String traceId, BigDecimal money) {
this.payMode.security(uid);
System.out.println("ali pay");
}
}
- 桥接模式支付测试(调用实现上优化了原有的if...else...逻辑)
public class BridgePayTest {
public static void main(String[] args) {
// 微信 + 指纹
Pay wechatPay = new WeChatPay(new FingerprintPayMode());
wechatPay.pay("1001","t001",new BigDecimal("100"));
// 支付宝 + 人脸识别
Pay alipayPay = new AliPay(new FacePayMode());
alipayPay.pay("1001","t002",new BigDecimal("300"));
}
}
-- output
指纹验证
wechat pay
人脸识别
ali pay
补充说明:桥接模式 VS 装饰器模式
结合上述分析,可以看到桥接模式的设计和装饰器模式的设计实现上非常相似,但可以从一点去加以区分理解:从代码实现上看装饰器模式下的装饰对象基类它还实现了被装饰对象的接口方法,也就是说装饰器模式有更强的目的性,目的是装饰指定接口实现,而桥接模式的基类接入的范围更加灵活
2.数据库连接(JDBC&DriverManager)
JDBC为所有关系型数据库提供了一个通用标准,这是一个桥接模式的典型应用,可以结合传统模式下JDBC的使用步骤进行理解
- JDBC使用:加载驱动、配置参数、创建数据库连接、创建Statement实例、执行SQL语句(处理结果)、关闭连接对象
加载驱动:查看加载驱动源码实现Class.forName("com.mysql.cj.jdbc.Driver");
,可以看到其是通过registerDriver
方法将MySQL驱动注入到DriverManager
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
获取数据库连接:Connection connection = DriverManager.getConnection(url, username, password);
,根据相应的驱动获取连接实例
public static Connection getConnection(String url,String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
// 实际上调用的是下面的静态方法getConnection
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
Powered by Waline v3.1.3