侧边栏壁纸
博主头像
qingtian博主等级

喜欢是一件细水流长的事,是永不疲惫的双向奔赴~!

  • 累计撰写 104 篇文章
  • 累计创建 48 个标签
  • 累计收到 1 条评论

再谈桥接模式

qingtian
2022-04-10 / 0 评论 / 0 点赞 / 658 阅读 / 6,483 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-04-10,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

桥接模式

桥接模式原理解析

桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。

桥接模式的解释:一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。通过组合关系来代替继承关系,避免继承层次的指数级爆炸。

我们如何利用JDBC驱动来查询数据库

Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=password";
        Connection con = DirverManager.getConnection(url);
        Statement stmt = con.createStatement();
        String query = "select * from test";
        ResultSet rs = stmt.executeQuery(query);
        while (rs.next()) {
            rs.getString(1);
            rs.getInt(2);
        }

如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 就可以了。当然,也有更灵活的实现方式,我们可以把需要加载的 Driver 类写到配置文件中,当程序启动的时候,自动从配置文件中加载,这样在切换数据库的时候,我们都不需要修改代码,只需要修改配置文件就可以了。

那么JDBC是如何优雅的完成数据库的切换呢?

看一下JDBC的源码是怎么写的吧。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

结合 com.mysql.jdbc.Driver 的代码实现,我们可以发现,当执行Class.forName(“com.mysql.jdbc.Driver”) 这条语句的时候,实际上是做了两件事情。第一件事情是要求 JVM 查找并加载指定的 Driver 类,第二件事情是执行该类的静态代码,也就是将 MySQL Driver 注册到 DriverManager 类中。

DriverManager 类是干什么用的。具体的代码如下所示。当我们把具体的 Driver 实现类(比如,com.mysql.jdbc.Driver)注册到 DriverManager 之后,后续所有对 JDBC 接口的调用,都会委派到对具体的 Driver 实现类来执行。而 Driver 实现类都实现了相同的接口(java.sql.Driver ),这也是可以灵活切换 Driver 的原因。

public class DriverManager {

    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

public static void registerDriver(java.sql.Driver driver)
        throws SQLException {

        registerDriver(driver, null);
    }

    /**
     * Registers the given driver with the {@code DriverManager}.
     * A newly-loaded driver class should call
     * the method {@code registerDriver} to make itself
     * known to the {@code DriverManager}. If the driver is currently
     * registered, no action is taken.
     *
     * @param driver the new JDBC Driver that is to be registered with the
     *               {@code DriverManager}
     * @param da     the {@code DriverAction} implementation to be used when
     *               {@code DriverManager#deregisterDriver} is called
     * @exception SQLException if a database access error occurs
     * @exception NullPointerException if {@code driver} is null
     * @since 1.8
     */
    public static void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if (driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

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

}

在JDBC的例子中,JDBC 本身就相当于“抽象”。注意,这里所说的“抽象”,指的并非“抽象类”或“接口”,而是跟具体的数据库无关的、被抽象出来的一套“类库”。具体的
Driver(比如,com.mysql.jdbc.Driver)就相当于“实现”。注意,这里所说的“实现”,也并非指“接口的实现类”,而是跟具体数据库相关的一套“类库”。JDBC 和Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。

桥接模式的应用举例

根据不同的告警规则,触发不同类型的告警。告警支持多种通知渠道,包括:邮件、短信、微信、自动语音电话。通知的紧急程度有多种类型,包括:SEVERE(严重)、URGENCY(紧急)、NORMAL(普通)、TRIVIAL(无关紧要)。不同的紧急程度对应不同的通知渠道。比如,SERVE(严重)级别的消息会通过“自动语音电话”告知相关人员。

先看一下最简单直接的设计

public enum NotificationEmergencyLevel {  
 SEVERE, URGENCY, NORMAL, TRIVIAL  
}
---------------------------------------------------------------------
public class Notification {
    private List<String> emailAddress;
    private List<String> telephone;
    private List<String> wechatIds;

    public Notification() {}

    public void setEmailAddress(List<String> emailAddress) {
        this.emailAddress = emailAddress;
    }

    public void setTelephone(List<String> telephone) {
        this.telephone = telephone;
    }

    public void setWechatIds(List<String> wechatIds) {
        this.wechatIds = wechatIds;
    }

    public void notify(NotificationEmergencyLevel level, String message) {
        if (level.equals(NotificationEmergencyLevel.SEVERE)) {
            //自动语音电话
        }else if (level.equals(NotificationEmergencyLevel.URGENCY)) {
            //发微信
        }else if (level.equals(NotificationEmergencyLevel.NORMAL)) {
            //发邮件
        }else if (level.equals(NotificationEmergencyLevel.TRIVIAL)) {
            //发邮件
        }
    }
}
--------------------------------------------------------------------
Notification类的实际应用
public class ErrorAlertHandler extends AlertHandler{

    private AlertRule rule;
    private Notification notification;

    public ErrorAlertHandler(AlertRule rule, Notification notification) {
        super(rule, notification);
    }

    @Override
    public void check(ApiStatInfo apiStatInfo) {
        if (apiStatInfo.getErrorCount() > rule.getMatchRule(apiStatInfo.getApi())) {
            notification.notify(NotificationEmergencyLevel.SEVERE, "message");
        }
    }
}

问题:

Notification 类的代码实现有一个最明显的问题,那就是有很多 if-else 分支逻辑。实际上,如果每个分支中的代码都不复杂,后期也没有无限膨胀的可能(增加更多 if-else 分支判断),那这样的设计问题并不大,没必要非得一定要摒弃 if-else 分支逻辑。

不过,Notification 的代码显然不符合这个条件。因为每个 if-else 分支中的代码逻辑都比较复杂,发送通知的所有逻辑都扎堆在 Notification 类中。我们知道,类的代码越多,就越难读懂,越难修改,维护的成本也就越高。

针对 Notification 的代码,我们将不同渠道的发送逻辑剥离出来,形成独立的消息发送类(MsgSender 相关类)。其中,Notification 类相当于抽象,MsgSender 类相当于实现,两者可以独立开发,通过组合关系(也就是桥梁)任意组合在一起。所谓任意组合的意思就是,不同紧急程度的消息和发送渠道之间的对应关系,不是在代码中固定写死的,我们可以动态地去指定(比如,通过读取配置来获取对应关系)。

public interface MsgSender {
    void send(String message);
}

-------------------------------------------------------------------------
public class TelephoneMsgSender implements MsgSender{
    private List<String> telephones;

    public TelephoneMsgSender(List<String> telephones) {
        this.telephones = telephones;
    }
    @Override
    public void send(String message) {
        //...发送消息
    }
}


public class EmailMsgSender implements MsgSender{

    //和TelephoneMsgSender相似逻辑
    @Override
    public void send(String message) {

    }
}

public class WechatMsgSender implements MsgSender{
    //和TelephoneMsgSender相似的逻辑

    @Override
    public void send(String message) {

    }
}
--------------------------------------------------------------------

public abstract class Notification {
    protected MsgSender msgSender;

    public Notification (MsgSender msgSender) {
        this.msgSender = msgSender;
    }

    public abstract void notify(String message);
}

public class SevereNotification extends Notification{
    public SevereNotification(MsgSender msgSender) {
        super(msgSender);
    }

    @Override
    public void notify(String message) {
        msgSender.send(message);
    }
}

public class TrivialNotification extends Notification{
    //和SevereNotification相似代码结构
}

public class NormalNotification extends Notification{
    //和SevereNotification相似代码结构
}

public class UrgencyNotification extends Notification{
    //和SevereNotification相似代码结构
}

对于桥接模式的理解

在桥接模式中存在抽象实现的两个概念,其中抽象指的并非是抽象类或是接口,而是被抽象出来的一套骨架代码,其中真正的业务逻辑需要用过组合的方式来委派给实现来完成。

而定义中的实现,也并非接口的实现类,而是的一套独立的类库抽象实现独立开发,通过对象之间的组合关系,组装在一起。

0

评论区