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

news2024/12/24 2:20:10

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.coloradmin.cn/o/1836073.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

银行数仓项目实战(三)--使用Kettle进行增量,全量抽取

文章目录 使用Kettle进行全量抽取使用Kettle进行增量抽取 使用Kettle进行全量抽取 一般只有项目初始化的时候会使用到全量抽取&#xff0c;全量抽取的效率慢&#xff0c;抽取的数据量大。 我们在第一次进行全量抽取的时候&#xff0c;要在表中新建一个字段记录抽取时间&#x…

CLIP: Learning Transferable Visual Models From Natural Language Supervision

1、引言 论文链接&#xff1a;ReadPaper 现在最先进的计算机视觉系统都是训练模型来预测一组固定的、预定义好的目标类别&#xff08;如 ImageNet 的 1000 类和 COCO 的 80 类&#xff09;。这种受限制的监督形式限制了它们的通用性和可用性&#xff0c;因为需要额外的标记数据…

【第18章】Vue实战篇之登录界面

文章目录 前言一、数据绑定1. 数据绑定2. 数据清空 二、表单校验1. 代码2. 展示 三、登录1.登录按钮2.user.js3. login 四、展示总结 前言 上一章完成用户注册&#xff0c;这一章主要做用户登录。 一、数据绑定 登录和注册使用相同的数据绑定 1. 数据绑定 <!-- 登录表单 -…

Unity制作透明材质直接方法——6.15山大软院项目实训

之前没有在unity里面接触过材质的问题&#xff0c;一般都是在maya或这是其他建模软件里面直接得到编辑好材质的模型&#xff0c;然后将他导入Unity里面&#xff0c;然后现在碰到了需要自己在Unity制作透明材质的情况&#xff0c;所以先搜索了一下有没有现成的方法&#xff0c;很…

ECharts 词云图案例二:创意蒙版应用

ECharts 词云图案例二&#xff1a;创意蒙版应用 引言 在数据可视化领域&#xff0c;ECharts 以其强大的功能性和灵活性&#xff0c;成为开发者和设计师的首选工具之一。继上一篇关于 ECharts 词云图的详细介绍后&#xff0c;本文将探索词云图的进阶应用——使用蒙版来创造更具…

VS+QT+OCC创建坐标界面

1、安装并配置好项目后&#xff0c;填写如下代码&#xff1a; #pragma once#include <Standard_Handle.hxx> #include <V3d_Viewer.hxx> #include <OpenGl_GraphicDriver.hxx> #include <WNT_Window.hxx> #include <V3d_View.hxx> #include <…

C++ 72 之 友元和类模版

#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; #include <string>// 写法2&#xff1a; // template<class T1, class T2> // class Students12;// 要提前用到Students12&#xff0c;需要在前面先让编译器见过Students12才可…

【C语言】回调函数 和 部分库函数的用法以及模拟实现

一、回调函数&#xff1a; 1、定义&#xff1a; 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另一个函数&#xff0c;当这个指针被用来调用其所指向的函数时&#xff0c;我们就说这是回调函数。 2、qsort的模拟实现…

高效、智能、安全:小型机房EasyCVR+AI视频综合监控解决方案

一、背景需求分析 随着信息技术的迅猛发展&#xff0c;小型机房在企事业单位中扮演着越来越重要的角色。为了确保机房的安全稳定运行&#xff0c;远程监控成为了必不可少的手段。 二、视频监控 视频监控是机房远程监控的重要组成部分。通过安装IP摄像机及部署视频监控系统Ea…

【Linux】基础IO——理解文件系统

1.理解文件系统 1.1.ls与stat 磁盘文件由两部分构成&#xff0c;分别是文件内容和文件属性。 文件内容就是文件当中存储的数据&#xff0c;文件属性就是文件的一些基本信息&#xff0c; 例如文件名、文件大小以及文件创建时间等信息都是文件属性&#xff0c;文件属性又被称…

android常用知识

透明activity样式&#xff1a; android:theme"android:style/Theme.Translucent.NoTitleBar.Fullscreen"这句代码&#xff0c;当你是建的empty activity project时&#xff0c;默认继承的是AppCompat这个类。所以在AndroidMifext.xml文件中用上述代码会导致程序错误&…

反馈型振荡器

目录 反馈型振荡器分类 基本工作原理 启动过程 “心脏”LC振荡 起振条件 平衡条件 稳定条件 互感耦合振荡器 电感三端LC振荡器 电容三端LC振荡器 串联改进电容三端式振荡器 并联改进电容三端式振荡器 相位平衡条件的判断准则 反馈型振荡器分类 基本工作原理 启动过…

心跳机制讲解及实例

什么是心跳机制 心跳机制出现在tcp长连接中&#xff0c;客户端和服务器之见定时发送一种特殊的数据包通知对方还在线&#xff0c;以确保tcp链接地可靠性&#xff0c;有可能tcp链接由于某些原因(列入网线被拔了&#xff0c;突然断电)导致客户端断了&#xff0c;但是服务器不知道…

使用高斯混合模型(GMM)进行猫狗音频聚类(Kaggle Audio Cats and Dogs)

Audio Cats and Dogs | Kaggle 目录 一、实验目标 二、数据分析 三、实验结果 四、改进方向 一、实验目标 数据集包括164个标注为猫的.wav文件&#xff0c;总共1323秒和113个标注为狗叫声的.wav文件&#xff0c;总共598秒&#xff0c;要求判别每个音频是狗叫还是猫叫 二、…

Springboot + Mybatis 实现sql打印

参照这个视频&#xff1a;https://www.bilibili.com/video/BV1MS411N7mn/?vd_source90ebeef3261cec486646b6583e9f45f5 实现mybatis对外暴露的接口Interceptor 使用Intercepts接口,这里的写法参照mybatis-plus中的拦截器写法 Intercepts({Signature(type Executor.class, m…

FPGA开发Vivado安装教程

前言 非常遗憾的一件事情是&#xff0c;在选修课程时我避开了FPGA&#xff0c;选择了其他方向的课程。然而&#xff0c;令我没有想到的是&#xff0c;通信项目设计的题目竟然使用FPGA&#xff0c;这简直是背刺。在仅有的半个月时间里&#xff0c;准备这个项目确实是非常紧张的…

Corrupt JPEG data: 2 extraneous bytes before marker 0xd9

场景 异常&#xff1a;Corrupt JPEG data: 2 extraneous bytes before marker 0xd9 python语言&#xff0c;CV2读图像数据集&#xff0c;训练目标检测模型。在数据集分批送入模型训练过程中&#xff0c;出现大片图片异常情况。 &#xff08;建议直接去看修复图像方法二&…

华翰传媒集团横店影视基地盛大开业,汇剧视界APP震撼发布

2024年6月1日上午&#xff0c;横店影视华翰传媒集团携手腾烨影视隆、明艺影视重举办了横店影视基地的开业庆典。这一盛事不仅标志着华翰传媒集团在影视行业发展的重要里程碑&#xff0c;更彰显了其深耕影视产业、致力于打造高质量影视内容的决心与目标。 活动盛况空前&#xff…

vivado PIP or SITE_PIP、PKGPIN_BYTEGROUP

PIP是Xilinx部件上用于路由连接或网络的设备对象。PIP 称为ARC的连接多路复用器可以编程为将一根电线连接到 另一个&#xff0c;从而将节点连接在一起&#xff0c;以形成中特定NET所需的路由 设计。 SITE_PIP&#xff0c;也称为路由BEL&#xff0c;是SITE内部的连接多路复用器&…

vcs覆盖率相关

查看覆盖率是由哪几个tc覆盖的 选择要查看的覆盖率点&#xff0c;右键选择 show xxx tests&#xff1b; 覆盖率的合并