桥接设计模式( 99%的面试官都不懂)(二)

mb628db8cb0e5b3
发布于 2022-6-2 16:33
浏览
0收藏

 

告警系统
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()).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 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的理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系,组装在一起
• 第二种理解方式,它非常类似我们之前讲过的“组合优于继承”设计原则,通过组合关系来替代继承关系,避免继承导致的指数级爆炸
桥接看着就像是面向接口编程这一原则的原旨:将实现与抽象分离。但这会让你疑惑:让两者独立变化的这部分说法。接口不应该保持稳定吗,为何要变化? 但你要注意,是多个维度独立变化。本文的告警案例,紧急度和警报方式是两个不同维度,可有不同组合方式。

这与slf4j日志门面设计有异曲同工之妙。slf4j有三个核心概念:

• logger这个日志记录器负责哪个类的日志
• appender日志打印到哪里
• encoder日志打印的格式
三个维度上可有不同实现,使用者可在每一维度上自定义多个实现,配置文件中将各个维度的某一实现组合在一起即可。logger相当于是桥接器,它的具类包含了appender和encoder的接口实例。

因此,桥接模式就是面向接口编程设计原则的集大成者。面向接口编程只是说在系统的某一功能上将接口和实现解耦,而桥接是详细的分析系统功能,将各独立的维度抽象出来,使用时按需组合。所以,若你单从面相接口编程的角度来看待问题,能轻易写出告警案例的第一种写法(仅抽象告警功能),但从桥接模式考虑才能写出第二种写法(将告警功能细分成严重程度、通知渠道两个维度,分别进行抽象,并在使用时进行自由组合)

 

文章转自公众号: JavaEdge

分类
标签
已于2022-6-2 16:33:57修改
收藏
回复
举报
回复
    相关推荐