硬肝系列:23种设计模式之桥梁模式

用示例代码来帮你了解桥梁模式

对于“设计模式”这个词大家肯定都不陌生,很多框架也用到了设计模式,但是大部分的开发者应该是没有深入的了解过,我准备硬肝下这23设计模式作为专题文章的开端,一共23种设计模式,我尽量在<23天肝完。

为什么要学习设计模式:https://blog.youkuaiyun.com/kaituozhe_sh/article/details/107922339

在我大学四年,对设计模式也没有什么概念,写代码就想着能实现就可以了,不会有设计模式那样的思想,但是当学习到了框架的时候,对于设计模式才有了一些更深入的了解,使用设计模式的代码在扩展性上会比暴力的代码更容易维护,特别是当一个程序猿离职了后,你去接手它的代码,里面是一大堆if else,这样真的会崩溃,修改都不知道从何下手
在这里插入图片描述

硬肝系列目录

创建型模式

23种设计模式之工厂模式

23种设计模式之抽象工厂模式

23种设计模式之建造者模式

23种设计模式之原型模式

23种设计模式之单例模式

结构型模式

23种设计模式之适配器模式

23种设计模式之桥梁模式

23种设计模式之代理模式

23种设计模式之外观模式

23种设计模式之装饰器模式

23种设计模式之享元模式

23种设计模式之组合模式

行为型模式

23种设计模式之责任链模式

23种设计模式之命令模式

23种设计模式之迭代器模式

23种设计模式之中介者模式

23种设计模式之备忘录模式

到目前为止、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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沉淀顶峰相见的PET

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值