设计模式:桥接模式,如何实现支持不同类型和渠道的消息推送系统

原理解析

桥接模式,也叫作桥梁模式,英文是 Bridge Design Pattern。这个模式可以说是 23 种设计模式中最难理解的模式之一了。这个模式共有两种不同的理解模式:

  • 第一种,在GoF 的《设计模式》中,理解为“将抽象和实现解耦,让它们可以独立变化”
  • 第二种,一个类存在两个(或者多个)独立变化的维度,我们通过组合方式,让这两个(或者多个)维度可以独立进行扩展。通过组合关系替代继承关系,避免继承层次的指数级爆炸

JDBC驱动是桥接模式的经典应用。它的使用方式:

Class.forName("com.mysql.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_pas"
Connection con = DriverManager.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 就可以了。

那这种优雅的数据库切换是如何实现的呢?

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("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 xxxx();
	//...
	static {
		loadInitialDrivers();
		println("JDBC DriverManager initialized");
	}
	//...
	public static synchronized void registerDriver(java.sql.Driver driver) throws
		if (driver != null) {
			registeredDrivers.addIfAbsent(new DriverInfo(driver));
		} else {
			throw new NullPointerException();
		}
	}
	
	public static Connection getConnection(String url, String user, String passwo
		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来执行。
在这里插入图片描述

应用举例

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

public enum NotificationEmergencyLevel {
	SEVERE, URGENCY, NORMAL, TRIVIAL
}
public class Notification {
	private List<String> emailAddresses;
	private List<String> telephones;
	private List<String> wechatIds;
	
	public Notification() {}
	
	public void setEmailAddress(List<String> emailAddress) {
		this.emailAddresses = emailAddress;
	}
	
	public void setTelephones(List<String> telephones) {
		this.telephones = telephones;
	}
	
	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)) {
			//...发邮件
		}
	}
}

//在API监控告警的例子中,我们如下方式来使用Notification类:
public class ErrorAlertHandler extends AlertHandler {
	
	public ErrorAlertHandler(AlertRule rule, Notification notification){
		super(rule, notification);
	}
	
	@Override
	public void check(ApiStatInfo apiStatInfo) {
		if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi())
			notification.notify(NotificationEmergencyLevel.SEVERE, "...");
		}
	}
}

(Notification 类中的三个成员变量通过set 方法来设置,但是这样的代码实现存在一个明显的问题,那就是 emailAddresses、telephones、wechatIds 中的数据有可能在 Notification 类外部被修改,那如何重构代码才能避免这种情况的发生呢?参数不多的情况可以在构造函数初始化(如果不想被修改那么就不要暴露set接口,这样的话初始化这些email,telephone和wechat的工作就放到构造函数里,用构造函数去初始化这些变量,这样初始化之后正常情况下外面没法修改。),如果参数较多 就可以使用建造者模式初始化。)

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代码结构类似,所以省略...
}

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);
	}
	@Override
	public 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 的理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是的一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。
  • 对于第二种理解方式,它非常类似我们之前讲过的“组合优于继承”设计原则,通过组合关系来替代继承关系,避免继承层次的指数级爆炸。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值