[反模式] 浅谈面向对象桥接模式与组合

283 阅读4分钟

需求背景

在我个人一直在反复阅读的<极客时间:设计模式之美>专栏中, 其中一篇文章<49|桥接模式: 如何实现支持不同类型和渠道的消息推送系统?>, 论述了关于桥接模式的定义/来由与实例.

这篇文章不会长篇复制原作者争哥的例子了, 只是写下我个人的一些思考. (一篇专栏也不贵, 坚持反复横跳地阅读下来, 你总能收获到一些东西的, 不是吗?)

文中引用的实例是一个API接口监控告警系统, 基本的功能如下:

  1. 告警支持多种通知渠道, 包括: 邮件/短信/微信/自动语音电话.
  2. 通知的紧急程度支持多种类型, 包括: SEVERE(严重)/URGENCY(紧急)/NORMAL(普通)/TRIVIAL(无关紧要)
  3. 不同的紧急程度对应不同的通知渠道, 比如, 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()

那么这里又引出下一个问题: 当我们使用优化后的客户端代码时, 原来的代码结构会变成什么样呢?

挖坑待续...