设计模式之美学习(三十八)

本文最后更新于:10 个月前

如何实现支持不同类型和渠道的消息推送系统?

桥接模式的原理解析

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

对于这个模式有两种不同的理解方式。在 GoF 的《设计模式》一书中,桥接模式被定义为:“将抽象和实现解耦,让它们可以独立变化。”在其他资料和书籍中,还有另外一种更加简单的理解方式:“一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”。

对于第一种 GoF 的理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起。

对于第二种理解方式,它非常类似“组合优于继承”设计原则,通过组合关系来替代继承关系,避免继承层次的指数级爆炸。

桥接模式的应用举例

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

先来看最简单、最直接的一种实现方式。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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()).getMaxErrorCount()) {
notification.notify(NotificationEmergencyLevel.SEVERE, "...");
}
}
}

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

不过,Notification 的代码显然不符合这个条件。因为每个 if-else 分支中的代码逻辑都比较复杂,发送通知的所有逻辑都扎堆在 Notification 类中。我们知道,类的代码越多,就越难读懂,越难修改,维护的成本也就越高。很多设计模式都是试图将庞大的类拆分成更细小的类,然后再通过某种更合理的结构组装在一起。

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

按照这个设计思路,我们对代码进行重构。重构之后的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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代码结构类似,所以省略...
}

总体上来讲,桥接模式的原理比较难理解,但代码实现相对简单。

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