尚硅谷设计模式学习(八)桥接模式

以手机操作问题引入桥接模式

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

 传统思路分析

传统方案解决手机操作问题分析

1)扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。

2)违反了单一职责原则,当我们增加手机样式时,要同时增加所有品牌的手机,这样增加了代码维护成本。

问题的根本原因在于我们试图在两个独立的维度上进行扩展。这在处理继承时是很常见的问题。

解决思路:通过抽取其中一个维度并使之成为独立的类层次, 这样就可以在初始类中引用这个新层次的对象, 从而使得一个类不必拥有所有的状态和行为。

一、桥接模式

1、基本介绍

将实现与抽象放在两个不同的层次中,使两个层次可以独立改变。

桥接模式基于类的最小设计原则,通过使用封装、聚合、继承等行为让不同的类承担不同的职责。

它的主要特点是把抽象与行为实现分离开,从而可以保持各部分的独立性以及功能扩展。

在这里插入图片描述

  • Abstraction为抽象化角色,定义出该角色的行为,同时保存一个对实现化角色的引用;
  • Implementor是实现化角色,它是接口或者抽象类,定义角色必需的行为和属性;
  • RefinedAbstraction为修正抽象化角色,引用实现化角色对抽象化角色进行修正;
  • ConcreteImplementor为具体实现化角色,实现接口或抽象类定义的方法或属性。

2、代码实现

品牌及其实现类

public interface Brade {
    //开机
    void open();
    //打电话;
    void call();
    //关机;
    void close();
}
public class HuaWei implements Brade{

    public void open() {
        System.out.println("华为手机开机");
    }

    public void call() {
        System.out.println("华为手机通话");
    }

    public void close() {
        System.out.println("华为手机关机");
    }
}
public class Vivo implements Brade{

    public void open() {
        System.out.println("Vivo手机开机");
    }

    public void call() {
        System.out.println("Vivo手机通话");
    }

    public void close() {
        System.out.println("Vivo手机关机");
    }
}
public class XiaoMi implements Brade{

    public void open() {
        System.out.println("小米手机开机");
    }

    public void call() {
        System.out.println("小米手机通话");
    }

    public void close() {
        System.out.println("小米手机关机");
    }
}

抽象类手机及其实现类

//抽象类 手机
public abstract class Phone {
    //聚合手机品牌;
    private Brade brade;

    public Phone(Brade brade) {
        this.brade = brade;
    }

    //调用品牌的方法;
    protected void open(){
        this.brade.open();
    }

    protected void call(){
        this.brade.call();
    }

    protected void close(){
        this.brade.close();
    }
}
public class Folded extends Phone {
    public Folded(Brade brade) {
        super(brade);
    }

    @Override
    public void open(){
        System.out.println("折叠手机");
        super.open();
    }

    @Override
    public void call(){
        System.out.println("折叠手机");
        super.call();
    }

    @Override
    public void close(){
        System.out.println("折叠手机");
        super.close();
    }
}
public class UpRight extends Phone {
    public UpRight(Brade brade) {
        super(brade);
    }

    @Override
    public void open(){
        System.out.println("直立手机");
        super.open();
    }

    @Override
    public void call(){
        System.out.println("直立手机");
        super.call();
    }

    @Override
    public void close(){
        System.out.println("直立手机");
        super.close();
    }
}

客户端

public class Client {
    public static void main(String[] args) {

        //选择折叠式华为手机操作
        Phone phone1=new Folded(new HuaWei());
        phone1.open();
        phone1.call();

        System.out.println("<------------------------------->");

        //使用直立式小米手机操作;
        Phone phone2 = new UpRight(new XiaoMi());
        phone2.call();
        phone2.close();

    }
}

结果:

2、桥接模式在 JDBC 的源码剖析

我们来张图了解下JDBC的使用框架大概是个什么样子

 

JDBC为所有的关系型数据库提供一个通用的标准,这就是一个桥接模式的典型应用。我们先回顾一下JDBC的使用,用JDBC连接MySQL数据库主要分为这样几步:

//1.加载MySQL驱动注入到DriverManager
Class.forName("com.mysql.cj.jdbc.Driver");
//2.提供JDBC连接的URL、用户名和密码
String url = "jdbc:mysql://localhost:3306/test_db?";
String username = "root";
String password = "root";
//3.创建数据库的连接
Connection connection = DriverManager.getConnection(url, username, password);
//4.创建statement实例
Statement statement = connection.createStatement();
//5.执行SQL语句
String query = "select * from test";  //查询语句,也可以换成CRUD的其他语句
ResultSet resultSet = statement.executeQuery(query);
//6.关闭连接对象
connection.close();

我们一步步来看,先看步骤1:

Class.forName("com.mysql.cj.jdbc.Driver");

查看对应的源码

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

是通过静态方法调用registerDriver()方法来将MySQL驱动注入到DriverManagerregisterDriver()方法具体如下:向registeredDrivers添加驱动

public static synchronized void registerDriver(java.sql.Driver driver)
    throws SQLException {
	//直接调用下面的同名静态方法
    registerDriver(driver, null);
}

public static synchronized void registerDriver(java.sql.Driver driver,DriverAction da)throws SQLException {
    /* registeredDrivers是一个list,用DriverInfo实例封装Driver */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);

}

 registeredDrivers静态变量其实是一个list:

public class DriverManager {
    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    //...
}

DriverInfo类中封装了java.sql.Driver接口:

class DriverInfo {

    final Driver driver;
    DriverAction da;
    DriverInfo(Driver driver, DriverAction action) {
        this.driver = driver;
        da = action;
    }
    //...
}

java.sql.Driver接口: 

public interface Driver {

    Connection connect(String url, java.util.Properties info)
        throws SQLException;

    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;
}

 再看步骤2、3,重点是步骤3

Connection connection = DriverManager.getConnection(url, username, password);

Connection接口是和特定数据库的连接会话,不同的数据库的连接会话都不相同:

public interface Connection  extends Wrapper, AutoCloseable {

    Statement createStatement() throws SQLException;
    //...
}

是通过DriverManager中的getConnection方法,从registeredDrivers进行选择对应数据库驱动下的连接实例,再通过数据库驱动获取连接

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

综上我们可以画出对应的类图:

Driver和Connection之间是通过DriverManager类进行桥连接的

那在JDBC的设计中,什么是“抽象”,什么是“实现”呢?JDBC抽象出来的那套API就是抽象,不同数据库服务商实现的Driver就是实现。所以桥接模式中的抽象并不是指一个接口,它可能是设计出来的一套跟数据库操作相关的API。而具体的实现也不是一个接口,也可能是一套API,就像Driver中的connect,execute等方法。其实调用到最后,DriverManager都是委托具体的Driver干活的(connect,execute)

参考文章:你知道桥接模式在JDBC中的应用吗_归斯君的博客-CSDN博客_jdbc桥接模式

【设计模式系列】餐补:桥接模式与JDBC的故事 - 掘金 (juejin.cn)

三、桥接模式的注意事项和细节

优点

(1)实现了抽象和实现部分的分离

桥接模式分离了抽象部分和实现部分,从而极大的提供了系统的灵活性,让抽象部分和实现部分独立开来,分别定义接口,这有助于系统进行分层设计,从而产生更好的结构化系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。

(2)更好的可扩展性

由于桥接模式把抽象部分和实现部分分离了,从而分别定义接口,这就使得抽象部分和实现部分可以分别独立扩展,而不会相互影响,大大的提供了系统的可扩展性。

(3)可动态的切换实现

由于桥接模式实现了抽象和实现的分离,所以在实现桥接模式时,就可以实现动态的选择和使用具体的实现。

(4)实现细节对客户端透明,可以对用户隐藏实现细节。

缺点

(1)桥接模式的引入增加了系统的理解和设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程。

(2)桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围有一定的局限性。

适用场景

  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

常见的应用场景

1)JDBC  驱动程序

2)银行转账系统

  • 转账分类:   网上转账,柜台转账,AMT  转账 
  • 转账用户类型:普通用户,银卡用户,金卡用户..

3)消息管理

  • 消息类型:即时消息,延时消息 
  • 消息分类:手机短信,邮件消息,QQ 消息...
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小鲁蛋儿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值