用示例代码来帮你了解桥梁模式
对于“设计模式”这个词大家肯定都不陌生,很多框架也用到了设计模式,但是大部分的开发者应该是没有深入的了解过,我准备硬肝下这23设计模式作为专题文章的开端,一共23种设计模式,我尽量在<23天肝完。
为什么要学习设计模式:https://blog.youkuaiyun.com/kaituozhe_sh/article/details/107922339
在我大学四年,对设计模式也没有什么概念,写代码就想着能实现就可以了,不会有设计模式那样的思想,但是当学习到了框架的时候,对于设计模式才有了一些更深入的了解,使用设计模式的代码在扩展性上会比暴力的代码更容易维护,特别是当一个程序猿离职了后,你去接手它的代码,里面是一大堆if else,这样真的会崩溃,修改都不知道从何下手
硬肝系列目录
创建型模式
结构型模式
行为型模式
到目前为止、23种设计模式的创建型模式已经给大家肝完了,现在我们进入到一个全新的章节,结构型模式!!!
什么是桥梁模式?为什么要提出桥梁模式?
桥梁模式是对象的结构模式,又称柄体模式和接口模式、桥梁模式的目的是:“将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者独立的变化”。我给大家筛出三个重点:抽象化、实现化和脱耦
1)抽象化
存在于多个实例中的共同的概念性联系,就是抽象化。作为一个过程,抽象化就是忽略一些信息,把原本是一个实体中内聚的“属性”,拿出来单独进行处理之后再内聚到原本的实体中
2)实现
即针对抽象化给出的具体实现
3)脱耦
耦合是指两个实体行为的某种强关联,如果将他们的强关联去掉,称为脱耦。脱耦是指将抽象化和实现化之间的耦合分开,或者将他们之间的强关联换成弱关联,最终实现高内聚、低耦合的效果
示意图:
这里的抽象化并不是我们所说的抽象类,而是将事物中可以单独存在的提取出来单独作为一个事物扩展,可以做到解耦合的目的
先给大家看一个图,
就拿我们平常最经常用到的支付来说,你去一个便利店买东西付款,肯定有很多种支付方式,但是支付方式下又有很多支付模式,比如人脸、指纹等,你说一个支付方式下要实现这么多支付接口会不会也太麻烦了,如果用我们之前的方法,支付宝人脸支付一个类支付宝指纹一个类…这样就有3x3=9个类,既然我们学习设计模式,我们后面写代码不仅要能实现,还要考虑到易维护、美观等条件,向上面的这种情况我们就可以使用桥梁模式对其进行重构
分析上面的那个属性哪些是可以解耦出来的,看哪些是重复的而且是与类没有直接关联的属性,上面一看就知道是支付方式,你看看当你选择了支付宝付款,下一步才到选择支付模式,这两步是分开操作的,为什么我们不把支付方式和支付模式解耦出来呢?
下面我给大家画好了图
这样我们调用了支付宝支付,然后就可以选择支付模式了,只需要3+3=6个类就可以完成所有功能,既方便了开发,也方便了代码的维护,何乐而不为,说的再多都不如上手敲一下代码,大家可以根据我写的代码自行参考,有什么不对的欢迎指正
首先,我们将支付模式抽象成一个接口,人脸、指纹、密码为它的实现类
package designModels.design_mode_07_BridgePattern;
public interface PayMode {
void type();
}
package designModels.design_mode_07_BridgePattern;
//人脸支付
public class facePayMode implements PayMode{
@Override
public void type() {
System.out.println("使用人脸支付");
}
}
package designModels.design_mode_07_BridgePattern;
//指纹支付
public class fingerPayMode implements PayMode{
@Override
public void type() {
System.out.println("使用指纹支付");
}
}
package designModels.design_mode_07_BridgePattern;
//密码支付
public class passwordPayMode implements PayMode{
@Override
public void type() {
System.out.println("使用密码支付");
}
}
下面这步就是桥梁模式重要的一步,将支付模式聚合到支付方式!!!定义一个支付方式抽象类,里面属性包含支付模式
package designModels.design_mode_07_BridgePattern;
public abstract class Pay {
//内聚支付模式
PayMode payMode;
public Pay(PayMode p){
payMode = p;
}
public abstract void payType();
}
然后具体的支付方式继承该抽象类
package designModels.design_mode_07_BridgePattern;
//支付宝支付
public class aliPay extends Pay{
public aliPay(PayMode p) {
super(p);
}
@Override
public void payType() {
System.out.println("使用支付宝支付--支付验证");
payMode.type();
System.out.println("使用支付宝支付--支付成功");
}
}
package designModels.design_mode_07_BridgePattern;
//微信支付
public class wechatPay extends Pay{
public wechatPay(PayMode p) {
super(p);
}
@Override
public void payType() {
System.out.println("使用微信支付--支付验证");
payMode.type();
System.out.println("使用微信支付--支付成功");
}
}
package designModels.design_mode_07_BridgePattern;
//云闪付支付
public class cloudPay extends Pay{
public cloudPay(PayMode p) {
super(p);
}
@Override
public void payType() {
System.out.println("使用云闪付支付--支付验证");
payMode.type();
System.out.println("使用云闪付支付--支付成功");
}
}
接下来我们来测试一下这串代码能不能成功,编写测试类
package designModels.design_mode_07_BridgePattern;
public class TestPay {
public static void main(String[] args) {
Pay aliPay = new aliPay(new facePayMode());
Pay wechatPay = new wechatPay(new fingerPayMode());
Pay cloudPay = new cloudPay(new facePayMode());
aliPay.payType();
wechatPay.payType();
cloudPay.payType();
}
}
测试结果:
这样我们通过重构模式将原本耦合的代码分离开来,转变了一种方式将代码内聚到另一部分中,完成了高内聚低耦合的思想,之后如果增加了多种支付方式和支付模式也是不会影响之前的代码,符合开闭原则
JDBC驱动器
桥梁模式在Java应用中的一个非常典型的例子就是JDBC驱动器,让我们透过源码来看看桥梁模式是怎么实现JDBC驱动器的,我们先来看啊可能下面这一段代码
String sql = "select * from user";
//装载驱动
Class.froName("com.mysql.jdbc.Driver");
//创建连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","user","password");
PreparedStatement preparedStatement = conn.prepareStatement(sql);
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
String userId = resultSet.getString("userId");
String password = resultSet.getString(1); //此方法会比较高效
}
//关闭
resultSet.close();
preparedStatement.close();
conn.close();
我们一步步来剖析上面代码的执行流程:
第一步、装载驱动
Class.froName("com.mysql.jdbc.Driver");
这里扩展一下这个Class.forName,Class.forName和ClassLoader类都能用于加载java类获得Class对象,那这二者究竟有什么区别呢?
答案是,ClassLoader只能将类填放进入JVM方法区,在Class.newInstance的时候才会加载类中的代码块,但是Class.forName可以直接加载类中的静态代码块,来看看mysql的Driver源码
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
//直接加载静态块中的代码
static {
try {
//将驱动注册进DriverManager
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
所以说Class.froName("com.mysql.jdbc.Driver");
就加载了代码块,包括新建、注册等操作,不去读源码真的不知道这些东西
第二步、通过调用DriverManager类的getConnection()去获取连接:
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test","user","password");
上面这段代码所对应的源码:
@CallerSensitive
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()));
}
通过上面的这一段代码,我们得到的是一个Connection对象,这个Connection对象其实就是提供给各个数据源的一个接口,也就是说我sun公司定义了一个接口规范,你们去负责实现它就行,让我们接下去看(getConnection(url, info, Reflection.getCallerClass()));
是怎么返回的Connection对象,先来看看源码,大家一定要坚持下来,这句也是说给我听的,一开始看的时候也把我整的挺蒙圈,加油加油,再坚持一会!!
// 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");
}
}
上面的源码主要就是通过我们预先注册进DriverManager的Driver驱动,来遍历所有的Driver,截取比较重要的一部分给你们看看
这个registeredDrivers为一个List
这个connect方法是哪里的呢?让我们看看MySQL的Driver类还继承了什么
就是上面的NonRegisteringDriver
类,里面实现了connect方法,给大家大致看一下
public Connection connect(String url, Properties info) throws SQLException {
if (url != null) {
if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:loadbalance://")) {
return this.connectLoadBalanced(url, info);
}
if (StringUtils.startsWithIgnoreCase(url, "jdbc:mysql:replication://")) {
return this.connectReplicationConnection(url, info);
}
}
Properties props = null;
if ((props = this.parseURL(url, info)) == null) {
return null;
} else if (!"1".equals(props.getProperty("NUM_HOSTS"))) {
return this.connectFailover(url, info);
} else {
try {
com.mysql.jdbc.Connection newConn = ConnectionImpl.getInstance(this.host(props), this.port(props), props, this.database(props), url);
return newConn;
} catch (SQLException var6) {
throw var6;
} catch (Exception var7) {
SQLException sqlEx = SQLError.createSQLException(Messages.getString("NonRegisteringDriver.17") + var7.toString() + Messages.getString("NonRegisteringDriver.18"), "08001", (ExceptionInterceptor)null);
sqlEx.initCause(var7);
throw sqlEx;
}
}
}
到这,我们就可以获得一个Connection对象了,这个对象是通过mysql的driver对象返回的,所以就可以通过这个Connection对象来进行获取statement等接下来的操作,图示:
那么桥梁模式到底是怎么影响JDBC的呢?我用一幅图大家就明白了
这幅图我给大家解释一下,首先Sun公司给各个数据源公司提供一个统一的接口Driver,里面怎么实现我不管,如com.mysql.jdbc.Driver
,按照我提供的接口规范来就行,用户的应用程序调用我JDBC的API来实现连接数据库的功能,然后我通过DriverManager来作为一个桥梁,将你mysql的Driver注册进去,相当于你mysql的这辆车拿到了我桥梁的通行证,这样我在DriverManager的registeredDrivers(List)也就是注册列表里就有你的信息,然后调用connect方法就可以得到一个Connection对象啦,这样的好处是,我如果想要换一个驱动怎么办,只需要更改class.forName(“驱动名”)即可,此后所有的方法,包括获取statement等等,都是由接口声明调用,但是底层返回的是接口实现类,通过用这种桥梁模式,我们可以很轻松地在不同的数据库连接中进行转化,如果没有桥梁模式,那我们需要换一个驱动的话需要更改Driver里的所有代码,这样显然是不现实的
完成:TO: 2021/3/20 18:40