需求背景
在我个人一直在反复阅读的<极客时间:设计模式之美>专栏中, 其中一篇文章<49|桥接模式: 如何实现支持不同类型和渠道的消息推送系统?>, 论述了关于桥接模式的定义/来由与实例.
这篇文章不会长篇复制原作者争哥的例子了, 只是写下我个人的一些思考. (一篇专栏也不贵, 坚持反复横跳地阅读下来, 你总能收获到一些东西的, 不是吗?)
文中引用的实例是一个API接口监控告警系统, 基本的功能如下:
- 告警支持多种通知渠道, 包括:
邮件/短信/微信/自动语音电话.- 通知的紧急程度支持多种类型, 包括:
SEVERE(严重)/URGENCY(紧急)/NORMAL(普通)/TRIVIAL(无关紧要)- 不同的紧急程度对应不同的通知渠道, 比如,
SERVERE(严重)界别的消息会通过自动语音电话告知相关人员
最后, 经过几步演变, 文中给出了最终例子:
代码实现
MsgSender
public interface MsgSender {
void send(Strig message);
}
public class TelephoneMsgSender implements MsgSender {
private List<String> telephones;
/* @AllArgsConstructor */
@Override
public void send(String message) {
// send to telephones.
}
}
public class EmailMsgSender implements MsgSender {
// 代码结构与TelephoneMsgSender雷同
}
public class WechatMsgSender implements MsgSender {
// 代码结构与TelephoneMsgSender雷同
}
Notifier
public abstract class AbstractNotifier {
protected MsgSender msgSender;
public Notifier(MsgSender msgSender) {
this.msgSender = msgSender;
}
public abstract void notify(String message);
}
public class SevereNotifier extends AbstractNotifier {
public SevereNotifier(MsgSender msgSender) {
super(msgSender);
}
@Override
public void notify(String message) {
msgSender.send(message);
}
}
public class UrgencyNotifier extends AbstractNotifier {
// 代码结构与SevereNotifier雷同
}
public class NormalNotifier extends AbstractNotifier {
// 代码结构与SevereNotifier雷同
}
public class TrivialNotifier extends AbstractNotifier {
// 代码结构与SevereNotifier雷同
}
延伸的思路
客户端
上面的代码结构结束后, 原始的文章就戛然而止了, 我个人总觉得似乎缺了些什么?
思索了一番, 应该是缺失了客户端(其实就是指使用上面Notifier的某段代码).
那么, 我尝试在这里补充一下:
public class NotifierTest {
@Test
public void testNotifier() {
List<AbstractNotifier> mixingNotifiers = Arrays.asList(
/* 严重消息:直接电话 */ new SevereNotifier(new TelephoneMsgSender()),
/* 严重消息:微信通知 */ new SevereNotifier(new WehcatMsgSender()),
/* 紧急消息:微信通知 */ new UrgencyNotifier(new WehcatMsgSender()),
/* 紧急消息:邮件通知 */ new UrgencyNotifier(new EmailMsgSender()));
for (AbstractNotifier notifier : mixingNotifiers) {
notifier.notify("混用消息");
}
List<AbstractNotifier> severeNotifiers = Arrays.asList(
/* 严重消息:直接电话 */ new SevereNotifier(new TelephoneMsgSender()),
/* 严重消息:微信通知 */ new SevereNotifier(new WehcatMsgSender()));
for (AbstractNotifier sNotifier : severeNotifiers) {
sNotifier.notify("严重消息");
}
List<AbstractNotifier> urgencyNotifiers = Arrays.asList(
/* 紧急消息:微信通知 */ new UrgencyNotifier(new WehcatMsgSender()),
/* 紧急消息:邮件通知 */ new UrgencyNotifier(new EmailMsgSender()));
for (AbstractNotifier uNotifier : urgencyNotifiers) {
uNotifier.notify("严重消息");
}
}
}
至此, 客户端代码补充完整, 但你发现了吗? 上述客户端代码, 仅仅是为了用Notifier而用Notifier, 实际的生产环境中, 你不可能用这样的代码去发送消息. 如果有, 请你追查下写出这种代码的人是不是已经被拍扁了?
有个脑洞: 反模式
上图左边, 就是上文中给过的桥接模式代码结构; 好笑的是我突然之间想到, 把组合关系反过来的话, 那就会是上图右边的样子. 继续把脑洞延伸下去, 如果反模式成立, 那么, 客户端代码大概是这样:
public class MsgSenderTest {
@Test
public void testMsgSender() {
List<MsgSender> mixingMsgSenders = Arrays.asList(
/* 直接电话:严重消息 */ new TelephoneMsgSender(new SevereNotifer()),
/* 直接电话:紧急消息 */ new TelephoneMsgSender(new UrgencyNotifer()),
/* 微信通知:紧急消息 */ new TelephoneMsgSender(new UrgencyNotifer()),
/* 微信通知:一般消息 */ new TelephoneMsgSender(new NormalNotifer()));
for (MsgSender msgSender : mixingMsgSenders) {
msgSender.send("混用消息");
}
}
}
脑洞就在这里停住吧, 这个脑洞个人认为还是挺有意思的, 你看看, 需求是不是也随着反模式而反了过来?
- (正) 不同的紧急程度对应不同的通知渠道, 比如,
SEVERE(严重)界别的消息会通过自动语音电话告知相关人员- (反) 不同的通知渠道可以发送不同的紧急消息, 比如,
自动语言电话, 可以发送SEVERE, 也可以发送Urgency;
其中内涵的深意, 充满了严密的逻辑.
客户端优化
实际上, 也不能因为上面的客户端代码, 就一顿嘲讽最初的桥接模式代码结构, 设计模式都是一层叠一层的, 最终会叠出最适合你需求的代码. 但如果你想一步登天, 一下子就写出完美的代码, 那就有些痴人说梦了, 像我这样的普通人, 还是一步一脚印地慢慢走, 路才会比较扎实.
那话又说回来, 上面的代码我们应该再怎么优化一下呢? 实际上, 你是否觉得SEVERE(严重)/URGENCY(紧急)/NORMAL(普通)/TRIVIAL(无关紧要), 与我们经常使用的logger的error/warn/info/debug很雷同呢? 都是标识消息级别的形容词.
那么, 这里其实logger经历过多年的实践, 它的客户端代码已经非常完美了, 因此我们可以做一个理想化对比:
| 消息级别 | logger客户端代码 | notifier客户端代码 |
|---|---|---|
| 严重 | log.error() | notify.severe() |
| 紧急 | log.warn() | notify.urgency() |
| 普通 | log.info() | notify.normal() |
| 无关紧要 | log.debug() | notify.trivial() |
那么这里又引出下一个问题: 当我们使用优化后的客户端代码时, 原来的代码结构会变成什么样呢?