怎么查网站关键词密度,wordpress 侧边收起,响应式网站发展,sspai 少数派wordpress上一篇文章我们学习了第一种结构型模式#xff1a;代理模式。它在不改变原始类#xff08;或者叫被代理类#xff09;代码的情况下#xff0c;通过引入代理类来给原始类附加功能。代理模式在平时的开发经常被用到#xff0c;常用在业务系统中开发一些非功能性需求#xf…上一篇文章我们学习了第一种结构型模式代理模式。它在不改变原始类或者叫被代理类代码的情况下通过引入代理类来给原始类附加功能。代理模式在平时的开发经常被用到常用在业务系统中开发一些非功能性需求比如监控、统计、鉴权、限流、事务、幂等、日志。
今天我们再学习另外一种结构型模式桥接模式。桥接模式的代码实现非常简单但是理解起来稍微有点难度并且应用场景也比较局限所以相对于代理模式来说桥接模式在实际的项目中并没有那么常用你只需要简单了解见到能认识就可以并不是我们学习的重点。
话不多说让我们正式开始今天的学习吧
桥接模式的原理解析
桥接模式也叫作桥梁模式英文是 Bridge Design Pattern。这个模式可以说是 23 种设计模式中最难理解的模式之一了。我查阅了比较多的书籍和资料之后发现对于这个模式有两种不同的理解方式。
当然这其中“最纯正”的理解方式当属 GoF 的《设计模式》一书中对桥接模式的定义。毕竟这 23 种经典的设计模式最初就是由这本书总结出来的。在 GoF 的《设计模式》一书中桥接模式是这么定义的“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就是“将抽象和实现解耦让它们可以独立变化。”
关于桥接模式很多书籍、资料中还有另外一种理解方式“一个类存在两个或多个独立变化的维度我们通过组合的方式让这两个或多个维度可以独立进行扩展。”通过组合关系来替代继承关系避免继承层次的指数级爆炸。这种理解方式非常类似于我们之前讲过的“组合优于继承”设计原则所以这里我就不多解释了。我们重点看下 GoF 的理解方式。
GoF 给出的定义非常的简短单凭这一句话估计没几个人能看懂是什么意思。所以我们通过 JDBC 驱动的例子来解释一下。JDBC 驱动是桥接模式的经典应用。我们先来看一下如何利用 JDBC 驱动来查询数据库。具体的代码如下所示
Class.forName(com.mysql.jdbc.Driver);//加载及注册JDBC驱动程序
String url jdbc:mysql://localhost:3306/sample_db?userrootpasswordyour_password;
Connection con DriverManager.getConnection(url);
Statement stmt con.createStatement()
String query select * from test;
ResultSet rsstmt.executeQuery(query);
while(rs.next()) {rs.getString(1);rs.getInt(2);
}如果我们想要把 MySQL 数据库换成 Oracle 数据库只需要把第一行代码中的 com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 就可以了。当然也有更灵活的实现方式我们可以把需要加载的 Driver 类写到配置文件中当程序启动的时候自动从配置文件中加载这样在切换数据库的时候我们都不需要修改代码只需要修改配置文件就可以了。
不管是改代码还是改配置在项目中从一个数据库切换到另一种数据库都只需要改动很少的代码或者完全不需要改动代码那如此优雅的数据库切换是如何实现的呢
源码之下无秘密。要弄清楚这个问题我们先从 com.mysql.jdbc.Driver 这个类的代码看起。我摘抄了部分相关代码放到了这里你可以看一下。
package com.mysql.jdbc;
import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {static {try {java.sql.DriverManager.registerDriver(new Driver());} catch (SQLException E) {throw new RuntimeException(Cant 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 CopyOnWriteArrayListDriverInfo registeredDrivers new CopyOnWriteArrayListDriverInfo();//...static {loadInitialDrivers();println(JDBC DriverManager initialized);}//...public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException {if (driver ! null) {registeredDrivers.addIfAbsent(new DriverInfo(driver));} else {throw new NullPointerException();}}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 来执行。
我画了一张图帮助你理解你可以结合着我刚才的讲解一块看。
桥接模式的应用举例
在第 16 节中我们讲过一个 API 接口监控告警的例子根据不同的告警规则触发不同类型的告警。告警支持多种通知渠道包括邮件、短信、微信、自动语音电话。通知的紧急程度有多种类型包括SEVERE严重、URGENCY紧急、NORMAL普通、TRIVIAL无关紧要。不同的紧急程度对应不同的通知渠道。比如SERVE严重级别的消息会通过“自动语音电话”告知相关人员。
在当时的代码实现中关于发送告警信息那部分代码我们只给出了粗略的设计现在我们来一块实现一下。我们先来看最简单、最直接的一种实现方式。代码如下所示
public enum NotificationEmergencyLevel {SEVERE, URGENCY, NORMAL, TRIVIAL
}public class Notification {private ListString emailAddresses;private ListString telephones;private ListString wechatIds;public Notification() {}public void setEmailAddress(ListString emailAddress) {this.emailAddresses emailAddress;}public void setTelephones(ListString telephones) {this.telephones telephones;}public void setWechatIds(ListString 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)) {//...发邮件}}
}//在API监控告警的例子中我们如下方式来使用Notification类
public class ErrorAlertHandler extends AlertHandler {public ErrorAlertHandler(AlertRule rule, Notification notification){super(rule, notification);}Overridepublic void check(ApiStatInfo apiStatInfo) {if (apiStatInfo.getErrorCount() rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) {notification.notify(NotificationEmergencyLevel.SEVERE, ...);}}
}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 ListString telephones;public TelephoneMsgSender(ListString telephones) {this.telephones telephones;}Overridepublic void send(String message) {//...}}public class EmailMsgSender implements MsgSender {// 与TelephoneMsgSender代码结构类似所以省略...
}public class WechatMsgSender implements MsgSender {// 与TelephoneMsgSender代码结构类似所以省略...
}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);}Overridepublic void notify(String message) {msgSender.send(message);}
}public class UrgencyNotification extends Notification {// 与SevereNotification代码结构类似所以省略...
}
public class NormalNotification extends Notification {// 与SevereNotification代码结构类似所以省略...
}
public class TrivialNotification extends Notification {// 与SevereNotification代码结构类似所以省略...
}重点回顾
好了今天的内容到此就讲完了。我们一块来总结回顾一下你需要重点掌握的内容。
总体上来讲桥接模式的原理比较难理解但代码实现相对简单。
对于这个模式有两种不同的理解方式。在 GoF 的《设计模式》一书中桥接模式被定义为“将抽象和实现解耦让它们可以独立变化。”在其他资料和书籍中还有另外一种更加简单的理解方式“一个类存在两个或多个独立变化的维度我们通过组合的方式让这两个或多个维度可以独立进行扩展。”
对于第一种 GoF 的理解方式弄懂定义中“抽象”和“实现”两个概念是理解它的关键。定义中的“抽象”指的并非“抽象类”或“接口”而是被抽象出来的一套“类库”它只包含骨架代码真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”也并非“接口的实现类”而是一套独立的“类库”。“抽象”和“实现”独立开发通过对象之间的组合关系组装在一起。
对于第二种理解方式它非常类似我们之前讲过的“组合优于继承”设计原则通过组合关系来替代继承关系避免继承层次的指数级爆炸。