设计模式-结构型-06-桥接模式

1、传统方式解决手机操作问题

现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图:
在这里插入图片描述
UML 类图

在这里插入图片描述
问题分析

  1. 扩展性问题(类爆炸):如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类;同样如果我们增加一个手机品牌,也要在各个手机样式类下增加
  2. 违反了单一职责原则:当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本
  3. 解决方案——使用桥接模式

2、桥接模式基本介绍

  1. 桥接模式(Bridge模式):一种结构型设计模式:将实现与抽象放在两个不同的类层次中,使两个层次可以独立改变
  2. Bridge模式基于类的最小设计原则,通过使用封装、聚合及继承等行为让不同的类承担不同的职责
  3. 它的主要特点是把抽象(Abstraction)与行为实现(Implementation)分离开来,从而可以保持各部分的独立性以及应对他们的功能扩展

举个例子说明下:

对于手机来说,我们可以根据手机品牌分类,也可以根据手机类型来分类。所以手机这个系统可以在这两个角度独立的变化,手机品牌的变化不影响手机类型的变化。桥接模式其实就是通过合成/聚合代替继承,实现了松耦合的、在各个不同角度的”独立地变化“。

原理类图
在这里插入图片描述
原理类图说明

  • Client:桥接模式的调用者
  • Abstraction:Abstraction 充当桥接类,维护了 Implementor,即 ConcreteImplementorA / ConcreteImplementorB
  • RefinedAbstraction:Abstraction 抽象类的子类
  • Implementor:行为实现类的接口
  • ConcreteImplementorA / ConcreteImplementorB:行为的具体实现类
  • 这里的抽象类和接口是聚合的关系,也是调用者和被调用者的关系

3、桥接模式解决手机操作问题

UML 类图
在这里插入图片描述
核心代码

// 行为接口——品牌接口
public interface Branch {
    void open();

    void call();

    void close();
}
// 行为实现类——华为品牌
public class Huawei implements Branch {
    @Override
    public void open() {
        System.out.println("华为手机开机");
    }

    @Override
    public void call() {
        System.out.println("华为手机打电话");
    }

    @Override
    public void close() {
        System.out.println("华为手机关机");
    }
}
// 行为实现类——小米品牌
public class Xiaomi implements Branch {
    @Override
    public void open() {
        System.out.println("小米手机开机");
    }

    @Override
    public void call() {
        System.out.println("小米手机打电话");
    }

    @Override
    public void close() {
        System.out.println("小米手机关机");
    }
}
// 行为实现类——苹果品牌
public class iPhone implements Branch {
    @Override
    public void open() {
        System.out.println("苹果手机开机");
    }

    @Override
    public void call() {
        System.out.println("苹果手机打电话");
    }

    @Override
    public void close() {
        System.out.println("苹果手机关机");
    }
}

// 桥接类——手机抽象类
public abstract class Phone {
    private Branch branch;

    public Phone(Branch branch) {
        this.branch = branch;
    }

    public void open() {
        branch.open();
    }

    public void call() {
        branch.call();
    }

    public void close() {
        branch.close();
    }
}
// 桥接子类——翻盖式手机
public class FlipPhone extends Phone {
    public FlipPhone(Branch branch) {
        super(branch);
        System.out.println("翻盖式手机");
    }

    @Override
    public void open() {
        super.open();
    }

    @Override
    public void call() {
        super.call();
    }

    @Override
    public void close() {
        super.close();
    }
}
// 桥接子类——滑盖式手机
public class SlidePhone extends Phone {
    public SlidePhone(Branch branch) {
        super(branch);
        System.out.println("滑盖式手机");
    }

    @Override
    public void open() {
        super.open();
    }

    @Override
    public void call() {
        super.call();
    }

    @Override
    public void close() {
        super.close();
    }
}
// 桥接子类——直立式手机
public class UprightPhone extends Phone {
    public UprightPhone(Branch branch) {
        super(branch);
        System.out.println("直立式手机");
    }

    @Override
    public void open() {
        super.open();
    }

    @Override
    public void call() {
        super.call();
    }

    @Override
    public void close() {
        super.close();
    }
}

public class Client {
    public static void main(String[] args) {
        Phone phone = new FlipPhone(new Huawei());
        phone.open();
        phone.call();
        phone.close();

        System.out.println("======================");
        phone = new FlipPhone(new Xiaomi());
        phone.open();
        phone.call();
        phone.close();

        System.out.println("======================");
        phone = new UprightPhone(new iPhone());
        phone.open();
        phone.call();
        phone.close();
    }
}

4、JDBC 源码分析

说起jdbc,我相信很多人都不陌生,在最开始的web项目中,我们常常用它来连接数据库执行sql语句,下面是一个连接mysql的例子:


/***    定义数据库连接辅助类*/
public class DBhelper {
    private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/test";
    private static final String USER = "root";
    private static final String PASSWORD = "123456";
    private Connection conn = null;
    private Statement st = null;
    private PreparedStatement ppst = null;
    private ResultSet rs = null;     /*加载驱动*/

    static {
        try {
            Class.forName(DRIVERNAME).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("加载驱动失败");
        }
    }     /*获取数据库连接*/

    public Connection getConn() {
        try {
            conn = DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
            System.out.println("获取数据库连接失败");
        }
        return conn;
    }     /*释放数据库连接*/

    public void releaseConn() {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
        }
        if (st != null) {
            try {
                st.close();
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
        }
        if (ppst != null) {
            try {
                ppst.close();
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                System.out.println(e.getMessage());
            }
        }
    }

测试一下:

public class TestJdbc {
    private Connection conn = null;
    private Statement st = null;
    private PreparedStatement ppst = null;
    private ResultSet rs = null;

    private List<Object> selectUser(User user) {
        List<Object> list = new ArrayList<>();
        DBhelper dBhelper = new DBhelper();
        conn = dBhelper.getConn();
        String sql = "select * from blog_user where login_num=" + user.getLoginNum() + " and password =" + user.getPassword();
        try {
            st = conn.createStatement();
            rs = st.executeQuery(sql);
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            while (rs.next()) {
                Map map = new HashMap();
                for (int i = 1; i <= columnCount; i++) {
                    map.put(rsmd.getColumnLabel(i), rs.getObject(i));
                }
                list.add(map);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            dBhelper.releaseConn();
        }
        return list;
    }

    public static void main(String[] args) {
        TestJdbc testJdbc = new TestJdbc();
        User user = new User();
        user.setLoginNum(123456);
        user.setPassword("123456");
        List list = testJdbc.selectUser(user);
    }
}

debug一下:

在这里插入图片描述
然而在实际开发中,我们常常用到的框架是mybatis,其实mybatis就是对jdbc的封装,在我们以后的开发中我们可能会遇到关于持久层的各种问题,我们理解了jdbc的原理,那么mybatis又有何难?

依然以mysql为例,首先,我们来看数据库驱动Driver的加载过程:

在上面的贴出的代码中,我们可以看到一个静态代码块,利用class.forName()加载这个驱动,如下图:

  private static final String DRIVERNAME = "com.mysql.cj.jdbc.Driver";

/*加载驱动*/

    static {
        try {
            Class.forName(DRIVERNAME).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("加载驱动失败");
        }
    }

我们点进这个驱动,发现它继承了一个父类,实现了一个接口,里面有一个方法----注册驱动, 如下图:

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!");
        }
    }
}

我们先来看父接口 Driver,如下图:

这是一个顶层接口(由于太长,注释删掉一部分),简单理解一下注释,我们就会明白,这是一个所有的驱动必须实现的方法,也就是说,这是一个java连接数据库的一个接口,一个规范,数据库有很多种,因此数据库驱动也分很多种,但是不管你是那种数据库驱动,必须都要实现这个接口,遵循这个规范,才能连接数据库,无疑给我们带来了很大的方便,更换数据库就代表着更换驱动,而所有驱动都实现了这个接口,那我们只需要在利用反射加载驱动的class.forName()方法中注明需要加载的驱动就ok了,这样就可以适配所有的数据库.

/**
 * 每个驱动程序类必须实现的接口。
 *  <P>Java SQL 框架允许多个数据库驱动程序。 <P>
 * 每个驱动程序都应提供一个实现 * 驱动程序接口的类。
 * <P>DriverManager 将尝试加载尽可能多的驱动程序,然后对于任何给定的连接请求,
 * 它将依次要求每个 * 驱动程序尝试连接到目标 URL。 <P>
 * 强烈建议每个 Driver 类都应该是 * 小且独立的,以便 Driver 类可以加载和 * 查询,而无需引入大量支持代码。
 * <P>加载 Driver 类时,它应该创建 * 本身的实例并将其注册到 DriverManager。
 * 这意味着 * 用户可以通过调用以下命令来加载和注册驱动程序:
 * <p> * {@code Class.forName(“foo.bah.Driver”)}
 */
public interface Driver {
    /**
     * 尝试与给定 URL 建立数据库连接。    *
     * 如果驱动程序意识到连接到给定 URL 的驱动程序类型错误,则应返回“null”。
     * 这很常见,因为当 * 要求 JDBC 驱动程序管理器连接到给定的 URL 时,
     * 它会依次将 * URL 传递给每个加载的驱动程序。    *
     */
    Connection connect(String url, java.util.Properties info) throws SQLException;

   /**
     * 检索驱动程序是否认为它可以打开与给定 URL 的连接 *。 
    * 通常,如果驱动程序 * 理解 URL 中指定的子协议,则返回 <code>true</code>,
    * 如果 * 不理解,则返回 <code>false</code>。
     */
    boolean acceptsURL(String url) throws SQLException;

    DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException;

    int getMajorVersion();

    int getMinorVersion();

    boolean jdbcCompliant();

    public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}

再看NonRegisteringDriver

它实现了Driver,实际上,它是mysql驱动的一部分,里面的一些方法是关于连接mysql数据库的一些配置细节,根据一些连接属性创建一个真正连接数据库的网络通道

public class NonRegisteringDriver implements Driver {
    public static String getOSName() {
        return Constants.OS_NAME;
    }

    public static String getPlatform() {
        return Constants.OS_ARCH;
    }

    static int getMajorVersionInternal() {
        return StringUtils.safeIntParse("8");
    }

    static int getMinorVersionInternal() {
        return StringUtils.safeIntParse("0");
    }

    public NonRegisteringDriver() throws SQLException {
    }     //接收url,验证url的合法性

    public boolean acceptsURL(String url) throws SQLException {
        try {
            return ConnectionUrl.acceptsUrl(url);
        } catch (CJException var3) {
            throw SQLExceptionsMapping.translateException(var3);
        }
    }

    /**
     * 根据给定的URL和属性信息建立数据库连接。
     *
     * @param url 数据库连接URL,用于指定连接的数据库类型和位置。
     * @param info 属性信息,包含登录数据库所需的用户名和密码等信息。
     * @return 返回与数据库建立的连接对象,如果无法建立连接则返回null。
     * @throws SQLException 如果建立连接过程中发生错误,则抛出SQLException。
     */
    public Connection connect(String url, Properties info) throws SQLException {
        try {
            try {
                // 检查URL是否被当前驱动接受,如果不接受则直接返回null。
                if (!ConnectionUrl.acceptsUrl(url)) {
                    return null;
                } else {
                    // 根据URL和属性信息获取ConnectionUrl实例,用于解析连接URL并确定连接类型。
                    //负载均衡式访问
                    ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
                    // 根据连接类型创建并返回相应的连接对象。
                    switch (conStr.getType()) {
                        case SINGLE_CONNECTION:
                            return ConnectionImpl.getInstance(conStr.getMainHost());
                        case FAILOVER_CONNECTION:
                        case FAILOVER_DNS_SRV_CONNECTION:
                            return FailoverConnectionProxy.createProxyInstance(conStr);
                        case LOADBALANCE_CONNECTION:
                        case LOADBALANCE_DNS_SRV_CONNECTION:
                            return LoadBalancedConnectionProxy.createProxyInstance(conStr);
                        case REPLICATION_CONNECTION:
                        case REPLICATION_DNS_SRV_CONNECTION:
                            return ReplicationConnectionProxy.createProxyInstance(conStr);
                        default:
                            return null;
                    }
                }
            } catch (UnsupportedConnectionStringException var5) {
                // 如果连接字符串不被支持,则返回null。
                return null;
            } catch (CJException var6) {
                // 如果在连接过程中发生CJException,则将其转换为SQLException并抛出。
                throw (UnableToConnectException) ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);
            }
        } catch (CJException var7) {
            // 如果发生CJException,则将其转换为SQLException并抛出。
            throw SQLExceptionsMapping.translateException(var7);
        }
    }

    public int getMajorVersion() {
        return getMajorVersionInternal();
    }

    public int getMinorVersion() {
        return getMinorVersionInternal();
    }

    /**
     * 获取驱动程序属性信息。
     * 该方法通过分析给定的URL和属性,构造驱动程序需要的属性信息。它主要用于配置连接到数据库所需的属性。
     *
     * @param url 数据库连接URL,用于解析数据库类型、主机、端口、数据库名称等信息。
     * @param info 已经存在的属性信息,可能包含主机、端口、数据库名称、用户和密码等信息。
     * @return DriverPropertyInfo数组,包含所有必要的驱动程序属性信息。
     * @throws SQLException 如果解析URL或获取属性信息过程中出现错误,则抛出SQLException。
     */
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        try {
            // 初始化数据库连接所需的各个属性
            String host = "";
            String port = "";
            String database = "";
            String user = "";
            String password = "";

            // 如果URL不为空,则尝试解析URL以获取更多配置信息
            if (!StringUtils.isNullOrEmpty(url)) {
                ConnectionUrl connStr = ConnectionUrl.getConnectionUrlInstance(url, info);
                // 如果是单个连接配置,提取主机信息
                if (connStr.getType() == Type.SINGLE_CONNECTION) {
                    HostInfo hostInfo = connStr.getMainHost();
                    // 将主机信息转换为Properties对象
                    info = hostInfo.exposeAsProperties();
                }
            }

            // 从info对象中提取出各个属性值
            if (info != null) {
                host = info.getProperty(PropertyKey.HOST.getKeyName());
                port = info.getProperty(PropertyKey.PORT.getKeyName());
                database = info.getProperty(PropertyKey.DBNAME.getKeyName());
                user = info.getProperty(PropertyKey.USER.getKeyName());
                password = info.getProperty(PropertyKey.PASSWORD.getKeyName());
            }

            // 创建驱动程序属性信息对象,并设置相应的属性和描述
            DriverPropertyInfo hostProp = new DriverPropertyInfo(PropertyKey.HOST.getKeyName(), host);
            hostProp.required = true;
            hostProp.description = Messages.getString("NonRegisteringDriver.3");

            DriverPropertyInfo portProp = new DriverPropertyInfo(PropertyKey.PORT.getKeyName(), port);
            portProp.required = false;
            portProp.description = Messages.getString("NonRegisteringDriver.7");

            DriverPropertyInfo dbProp = new DriverPropertyInfo(PropertyKey.DBNAME.getKeyName(), database);
            dbProp.required = false;
            dbProp.description = Messages.getString("NonRegisteringDriver.10");

            DriverPropertyInfo userProp = new DriverPropertyInfo(PropertyKey.USER.getKeyName(), user);
            userProp.required = true;
            userProp.description = Messages.getString("NonRegisteringDriver.13");

            DriverPropertyInfo passwordProp = new DriverPropertyInfo(PropertyKey.PASSWORD.getKeyName(), password);
            passwordProp.required = true;
            passwordProp.description = Messages.getString("NonRegisteringDriver.16");

            // 初始化JDBC属性集,并根据当前配置初始化属性
            JdbcPropertySet propSet = new JdbcPropertySetImpl();
            propSet.initializeProperties(info);

            // 将JDBC属性集暴露为驱动程序属性信息
            List<DriverPropertyInfo> driverPropInfo = propSet.exposeAsDriverPropertyInfo();

            // 创建一个包含所有属性信息的数组
            DriverPropertyInfo[] dpi = new DriverPropertyInfo[5 + driverPropInfo.size()];

            // 将基本属性和额外的驱动程序属性信息添加到数组中
            dpi[0] = hostProp;
            dpi[1] = portProp;
            dpi[2] = dbProp;
            dpi[3] = userProp;
            dpi[4] = passwordProp;
            System.arraycopy(driverPropInfo.toArray(new DriverPropertyInfo[0]), 0, dpi, 5, driverPropInfo.size());

            // 返回包含所有属性信息的数组
            return dpi;
        } catch (CJException var17) {
            // 将内部CJException转换为SQLException并抛出
            throw SQLExceptionsMapping.translateException(var17);
        }
    }

    public boolean jdbcCompliant() {
        return false;
    }

    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        throw new SQLFeatureNotSupportedException();
    }

    static {
        try {
            Class.forName(AbandonedConnectionCleanupThread.class.getName());
        } catch (ClassNotFoundException var1) {
        }
    }
}

然后就是 DriverManager.registerDriver( new Driver() ) 这个注册驱动的方法,它是将自己传给DriverManager,我们点开这个方法:
这里将Driver封装进DriverInfo类中,添加在DriverManager的静态List中,便于DriverManager管理驱动

 /**
     * class DriverManager
     * 这段代码定义了一个私有的、静态的、常量registeredDrivers,其类型为CopyOnWriteArrayList<DriverInfo>。这个列表用于存储DriverInfo类型的元素。
     * CopyOnWriteArrayList是Java并发编程中的一种线程安全的列表实现。它通过使用“写时复制”(Copy-on-Write)的策略来实现并发访问和修改。当有线程尝试修改列表时,会创建该列表的一个副本,并在副本上进行修改操作,而原列表则保持不变。这样可以确保在并发环境下,读操作的高效性和线程安全性。
     * 在该代码中,registeredDrivers列表用于存储DriverInfo类型的元素,可以进行元素的添加、删除和查询等操作。由于使用了CopyOnWriteArrayList,因此在并发环境下对列表的操作是线程安全的。
     */
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

    public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {
        registerDriver(driver, null);
    }

    /**
     * 注册驱动程序
     * 该函数用于向DriverManager注册给定的 JDBC 驱动程序。
     * 如果驱动程序已经注册,则不会采取任何行动。该方法接受两个参数:
     * driver是要注册的 JDBC 驱动程序,
     * da是当调用DriverManager#deregisterDriver时使用的DriverAction实现。
     * 如果driver为null,则会抛出NullPointerException。
     * 如果注册成功,则会在控制台打印registerDriver:加上驱动程序的信息。
     * @param driver
     * @param da
     * @throws SQLException
     */
    public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
        /* 如果驱动程序尚未添加到我们的列表中,请注册它 */
        if (driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            //这是为了与原始 DriverManager 兼容
            throw new NullPointerException();
        }
        println("registerDriver: " + driver);
    }

再点开: registeredDrivers.addIfAbsent(new DriverInfo(driver, da))
方法名大概是说 如果缺席(没有)就添加类里面有一个array,看注释,这是一个放置驱动类的临时数组,只能通过getArraysetArray获取和设置.

indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :addIfAbsent(e, snapshot)是返回参数e 在数组snapshot 的下标,这里的 e 就是 上面的new DriverInfo(driver, da), snapshot 为上面提到的 array
当e为null,返回snapshot中null的下标.如果snapshot中没有e,则返回 -1,也就是说其实是判断snapshot中有没有e,没有的话,就调用方法添加.

再看下面的添加方法,就是把 Driver 放进 array 中,相当于把驱动注册进DriverManager中,至于这里为什么是一个数组?假如我们一个系统同时连接两种或者多种数据库,那我们就需要多个驱动,因此这里是一个数组,当我们需要连接哪种数据库的时候,就可以从这里取出对应的驱动去获取连接


    private transient volatile Object[] array;

    /**
     * 查找元素在数组中的索引。
     *
     * @param o 要查找的元素,可以为null。
     * @param elements 目标数组,可能包含null元素。
     * @param index 搜索的起始索引。
     * @param fence 搜索的结束界限,但不包括该索引本身。
     * @return 如果找到元素,返回其索引;如果未找到,返回-1。
     *
     * 方法首先检查要查找的元素是否为null,然后遍历数组从指定索引开始直到指定的界限。
     * 如果元素为null,则查找数组中的null元素;如果元素不为null,则使用equals方法进行匹配查找。
     * 这种方法允许在数组中高效地查找特定元素,无论元素是否为null。
     */

    private static int indexOf(Object o, Object[] elements, int index, int fence) {
        if (o == null) {
            // 如果要查找的元素为null,则在数组中查找null元素。
            for (int i = index; i < fence; i++) if (elements[i] == null) return i;
        } else {
            // 如果要查找的元素不为null,则使用equals方法进行匹配查找。
            for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i;
        }
        // 如果未找到匹配的元素,返回-1。
        return -1;
    }


    /**
     * 在集合中添加元素e,仅当e不存在于集合中时添加。
     *
     * @param e 要添加到集合中的元素。
     * @return 如果元素已存在,则返回false;如果元素成功添加,则返回true。
     */
    public boolean addIfAbsent(E e) {
        // 获取当前集合的元素数组快照,用于后续判断元素是否已存在
        Object[] snapshot = getArray();
        // 判断元素e是否已存在于集合中
        if (indexOf(e, snapshot, 0, snapshot.length) >= 0) {
            return false; // 元素已存在,不添加,返回false
        } else {
            // 元素不存在,调用addIfAbsent方法实际添加元素
            return addIfAbsent(e, snapshot);
        }
    }


    /**
     * 该函数在同步锁定的情况下,检查给定的元素e是否存在于数组中。
     * 如果不存在,则将e添加到数组中,并返回true;
     * 如果存在,则返回false。函数首先比较给定的快照数组snapshot和当前数组是否相等,
     * 如果不相等,则遍历两个数组中长度较小的部分,
     * 检查e是否已经存在。如果e不存在,则继续查找剩余部分。
     * 如果e仍然不存在,则创建一个新数组,将e添加到新数组末尾,
     * 然后将新数组设置为当前数组。最后返回true表示成功添加元素。
     *
     * @param e 要添加到集合的元素。
     * @param snapshot 快照数组,用于比较以确定元素是否已经存在。
     * @return 如果元素成功添加到集合中,则返回true;如果元素已经存在,则返回false。
     */
    private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock(); // 获取锁以确保线程安全
        try {
            Object[] current = getArray(); // 获取当前的元素数组
            int len = current.length; // 获取当前数组的长度
            if (snapshot != current) { // 检查快照数组是否是当前的元素数组
                // 针对另一个 addXXX 操作的失败竞争进行了优化              
                int common = Math.min(snapshot.length, len); // 计算快照数组和当前数组的长度的较小值
                for (int i = 0; i < common; i++) {
                    if (current[i] != snapshot[i] && eq(e, current[i])) {
                        return false; // 如果在相同位置上元素不相同且新元素已存在,则返回false
                    }
                }
                if (indexOf(e, current, common, len) >= 0) {
                    return false; // 如果新元素在当前数组的剩余部分中存在,则返回false
                }
            }
            Object[] newElements = Arrays.copyOf(current, len + 1); // 创建一个新数组,长度为当前数组长度加一
            newElements[len] = e; // 将新元素添加到新数组的末尾
            setArray(newElements); // 设置新数组为当前的元素数组
            return true; // 返回true,表示新元素已成功添加
        } finally {
            lock.unlock(); // 释放锁
        }
    }

我们再看如何获取连接Connection, DriverManager遍历其中的所有的驱动,然后获取该驱动的连接,这种方法或许有点笨,但是可以兼容所有的数据库驱动,而这里真正连接数据库的操作Connection con = aDriver.driver.connect(url, info);调用的是NonRegisteringDriver中的connect()方法,返回一个Connection实例

 /**
     * 根据给定的URL、属性和调用者类加载器获取一个数据库连接。
     * 此方法是getConnection方法的实现,它尝试通过已注册的驱动程序来建立连接。
     * 
     * @param url 数据库连接URL,不能为null。
     * @param info 连接属性,可以为null。
     * @param caller 调用者的类,用于获取类加载器,可以为null。
     * @return 数据库连接对象。
     * @throws SQLException 如果无法建立连接或URL为null。
     */
    //  Worker method called by the public getConnection() methods.   
    private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
        // 根据调用者类加载器获取合适的类加载器
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        // 确保线程安全地处理类加载器
        synchronized (DriverManager.class) {           
            // 同步加载正确的类加载器。      
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
        // 检查URL是否为null
        if (url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }
        // 打印获取连接的日志信息
        println("DriverManager.getConnection(\"" + url + "\")");
        SQLException reason = null;
        // 尝试通过每个已注册的驱动程序建立连接
        for (DriverInfo aDriver : registeredDrivers) {
            // 检查驱动程序是否允许被调用者使用
            if (isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    // 尝试连接到数据库
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // 连接成功,返回连接对象
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    // 记录第一个发生的SQLException
                    if (reason == null) {
                        reason = ex;
                    }
                }
            } else {
                // 跳过不被允许的驱动程序
                println("    skipping: " + aDriver.getClass().getName());
            }
        }
        // 如果有SQLException发生,抛出该异常
        if (reason != null) {
            println("getConnection failed: " + reason);
            throw reason;
        }
        // 如果没有找到合适的驱动程序,抛出SQLException
        println("getConnection: no suitable driver found for " + url);
        throw new SQLException("No suitable driver found for " + url, "08001");
    }

再来看返回的连接 Connection,这也是一个接口,定义了一些数据库连接都要有的方法会用到的方法,我们看一下它的继承类图,如下:

package java.sql;

import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * <P>与特定 * 数据库的连接(会话)。在连接上下文中执行 SQL 语句并返回结果 *。* 
 * <pre> * java.util.Map map = con.getTypeMap(); 
 * * map.put(“mySchemaName.ATHLETES”, Class.forName(“运动员”)); 
 * * con.setTypeMap(地图); * </pre> 
 * * * @see DriverManager#getConnection 
 * * @see语句 
 * * @see ResultSet * @see DatabaseMetaData
 */
public interface Connection extends Wrapper, AutoCloseable {

//所有的数据库连接都要有的方法     

    Statement createStatement() throws SQLException;

    PreparedStatement prepareStatement(String sql) throws SQLException;

    CallableStatement prepareCall(String sql) throws SQLException;

    String nativeSQL(String sql) throws SQLException;

    void setAutoCommit(boolean autoCommit) throws SQLException;

    boolean getAutoCommit() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;

    boolean isClosed() throws SQLException;
}

public interface MysqlConnection {    //略    
    // 关于mysql连接的一些方法
}

public interface JdbcConnection extends Connection, MysqlConnection, TransactionEventHandler {
    //略
}

public class ConnectionImpl implements JdbcConnection, SessionEventListener, Serializable {
    //略
}

根据上面的分析,如图所示:
在这里插入图片描述

JDBC 的 Driver 接口:如果从桥接模式来看,Driver 就是一个接口,下面可以有 MySQL 的 Driver、Oracle 的 Driver,这些就可以当做实现接口类
在这里插入图片描述

在这里插入图片描述
Connection 继承体系

在这里插入图片描述

Driver源码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

DriverManager 结构

在这里插入图片描述
说明

  • MySQL 有自己的 Connectionlmpl 类,同样 Oracle 也有对应的实现类
  • Driver 和 Connection 之间是通过 DriverManager 类进行桥连接的

5、注意事项和细节

  1. 实现了抽象和实现部分的分离,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来。这有助于系统进行分层设计,从而产生更好的结构化系统
  2. 对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成
  3. 桥接模式替代多层继承方案,可以减少子类的个数,降低系统的管理和维护成本
  4. 桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程
  5. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的后限性,即需要有这样的应用场景

6、桥接模式其他应用场景

对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用

常见的应用场景

  1. JDBC 驱动程序
  2. 银行转账系统
    • 转账分类:网上转账、柜台转账、AMT 转账
    • 转账用户类型:普通用户、银卡用户、金卡用户
  3. 消息管理
    • 消息类型:即时消息、延时消息
    • 消息分类:手机短信、邮件消息、QQ消息…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/721562.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

yt-dlp:强大的跨平台视频下载器

一、引言 在当今数字时代&#xff0c;视频已成为我们获取信息和娱乐的重要途径。然而&#xff0c;由于版权和网络限制&#xff0c;我们常常无法直接在本地保存我们喜爱的视频。幸运的是&#xff0c;有一个名为yt-dlp的命令行程序&#xff0c;它可以帮助我们从YouTube.com和其他…

RK3588/算能/Nvidia智能盒子:加速山西铝业智能化转型,保障矿业皮带传输安全稳定运行

近年来&#xff0c;各类矿山事故频发&#xff0c;暴露出传统矿业各环节的诸多问题。随着全国重点产煤省份相继出台相关政策文件&#xff0c;矿业智能化建设进程加快。皮带传输系统升级是矿业智能化的一个重要环节&#xff0c;同时也是降本增效的一个重点方向。 △各省份智能矿山…

CCAA:认证通用基础(国家质量基础设施(NQI))的相关概念

3.国家质量基础设施NQI&#xff09;的相关概念 一、国家质量基础设施 国家质量基础设施(NQI&#xff0c;NationalQualityInfrastructure):国家建立和执行标准、计量认证认可、检验检测等所需的质量体制框架的统称&#xff0c;包括法规体系、管理体系、技术体系等质量基础设施与…

猫头虎 分享已解决Bug || `Uncaught ReferenceError: x is not defined`✨

猫头虎 分享已解决Bug || Uncaught ReferenceError: x is not defined&#x1f680;✨ 摘要 ✨&#x1f4a1; 大家好&#xff0c;我是猫头虎&#xff0c;一名全栈软件工程师&#xff0c;同时也是一位科技自媒体博主。今天我要和大家分享一些前端开发过程中常见的Bug以及详细的…

国内能用的ai聊天软件有哪些?这三款还不错

国内能用的ai聊天软件有哪些&#xff1f;在人工智能飞速发展的今天&#xff0c;AI聊天软件已经成为我们日常生活和工作中不可或缺的一部分。它们不仅可以帮助我们快速获取信息&#xff0c;还能提供有趣的对话体验。今天&#xff0c;就为大家推荐三款国内能用的AI聊天软件&#…

分布式事务msb

分布式事务使用场景 添加商品看库存够不够。库存扣减&#xff0c;扣完给订单服务一个响应&#xff0c;如果新加商品出问题了怎么回滚。 分布式事务概念 XA规范 XA规范&#xff1a;总之一句话&#xff1a; 就X/Open DTP 定义的 事务协调者与数据库之间的接口规范&#xff08;即…

港硕上岸鹅厂算法岗,谈谈感受和心得!

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、算法项目落地经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接如…

目标检测—Fast RCNN

介绍Fast R-CNN之前先简单回顾一下R-CNN和SPP-net R-CNN&#xff08;Regions with CNN&#xff09; affine image wraping 解析 Bounding Box Regression&#xff08;边界框回归&#xff09; 如何回归&#xff1f; 问题1&#xff1a;为什么要使用相对坐标差&#xff1f; …

数据库系统概述选择简答概念复习

目录 一、组成数据库的三要素 二、关系数据库特点 三、三级模式、二级映像 四、视图和审计提供的安全性 审计(Auditing) 视图(Views) 五、grant、revoke GRANT REVOKE 六、三种完整性 实体完整性 参照完整性 自定义完整性 七、事务的特性ACDI 原子性(Atomicity)…

A.P.穆勒-马士基将作为银牌赞助商出席2024中国汽车供应链降碳和可持续国际峰会

作为一家综合性集装箱物流公司&#xff0c;A.P.穆勒-马士基致力于连接和简化我们客户的供应链。作为物流服务领域的全球领导者&#xff0c;公司拥有100,000多家客户&#xff0c;业务遍及130多个国家&#xff0c;拥有约80,000名员工。A.P.穆勒-马士基致力于通过新技术、新船舶和…

c++实现二叉搜索树(下)

好久不见啊&#xff0c;baby们&#xff0c;小吉我又回归了&#xff0c;发完这一篇小吉将会有两周时间不会更新blog了&#xff08;sorry&#xff09;&#xff0c;在小吉没有发blog的日子里大家也要好好学习数据结构与算法哦&#xff0c;还有就是别忘了小吉我❤️  这篇博客是二…

MySQL从5.7升级到8.0步骤及其问题

MySQL从5.7升级到8.0步骤及其问题 前言 本文源自微博客&#xff0c;且以获得授权&#xff0c;请尊重版权。 一、需求背景 Docker环境下&#xff0c;MySQL5.7升级到8.0&#xff0c;数据迁移时使用的是mysqldump方式迁移。 二、迁移步骤 数据备份&#xff1a; docker exec -i 1…

pygame游戏开发

Pygame游戏开发 pygame简介 模块库请参考&#xff1a;pygame官方文档 pygame可以用作游戏开发&#xff0c;但在商业游戏中真正的开发工具却不是pygame。使用pygame开发游戏周期长。 安装pygame 在pycharm中安装第三方库pygame&#xff1a; 在计算机中安装pygame&#xf…

AI虚拟数字人上线需要办理哪些资质?

近年来&#xff0c;随着AI 技术快速发展&#xff0c;虚拟数字人行业也进入了新的发展阶段。AI 技术可覆盖虚拟数字人的建模、视频生成、驱动等全流程&#xff0c;一方面使虚拟数字人的制作成本降低、制作周期缩短&#xff0c;另一方面&#xff0c;多模态 AI 技术使得虚拟数字人…

【RK3588/算能/Nvidia智能盒子】AI“值守”,规范新能源汽车充电站停车、烟火及充电乱象

近年来&#xff0c;中国新能源汽车高速发展&#xff0c;产量连续8年位居全球第一。根据中国充电联盟数据&#xff0c;截至2023年6月&#xff0c;新能源汽车保有量1620万辆&#xff0c;全国充电基础设施累计数量为665.2万台&#xff0c;车桩比约2.5:1。 虽然新能源汽车与充电桩供…

短视频矩阵系统:高效运营,解决多账号管理难题

前言 在当下短视频风靡的时代&#xff0c;如何高效管理和运营多个短视频账号&#xff0c;成为了众多运营者面临的挑战。而今&#xff0c;一款全新的短视频矩阵系统应运而生&#xff0c;它不仅融合了AI文案生成与剪辑模式等先进功能&#xff0c;更支持多平台授权&#xff0c;助…

小阿轩yx-Nginx 优化与防盗链

小阿轩yx-Nginx 优化与防盗链 Nginx 服务优化 在企业应用环境中&#xff0c;服务器的安全性和响应速度需要根据实际情况进行相应参数配置&#xff0c;以达到最优的用户体验。 Nginx默认的安装参数 只能提供最基本的服务 需要调整 网页缓存时间连接超时网页压缩等相应参数…

【记录44】【案例】echarts地图

效果&#xff1a;直接上效果图 环境&#xff1a;vue、echarts4.1.0 源码 // 创建容器 <template><div id"center"></div> </template>//设置容器大小&#xff0c;#center { width: 100%; height: 60vh; }这里需注意&#xff1a;笔者在echar…

如何轻松进行照片压缩?5个软件帮助你快速进行照片压缩

如何轻松进行照片压缩&#xff1f;5个软件帮助你快速进行照片压缩 照片压缩是一种常见的图像处理操作&#xff0c;旨在减小图像文件的大小而尽量保持图像质量。有许多软件和工具可供选择&#xff0c;每个工具都有其独特的压缩算法和功能。以下是一些关于照片压缩的详细信息&am…

微信小程序毕业设计-小区疫情防控系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…