Skip to content

Latest commit

 

History

History
222 lines (172 loc) · 8.6 KB

《设计模式之美》之SOLID-O-开闭原则.md

File metadata and controls

222 lines (172 loc) · 8.6 KB

《设计模式之美》之SOLID-O-开闭原则

本课程作者认为,开闭原则是SOLID中最难理解、最难掌握,也是最有用的原则

开闭原则英文叫做Open Closed Principle,简称OCP

用中文描述该原则就是

添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)

需要记住的是该条原则的设计初衷:

运用该原则设计代码,使得在尽可能不破坏原代码逻辑(也包括单元测试等)的情况下,扩展新的功能

最难理解

之所以说这条原则难理解,那是因为,“怎样的代码改动才被定义为‘扩展’?怎样的代码改动才被定义为‘修改’?怎么才算满足或违反‘开闭原则’?修改代码就一定意味着违反‘开闭原则’吗?”等等这些问题,都比较难理解。

最难掌握

之所以说这条原则难掌握,那是因为,“如何做到‘对扩展开放、修改关闭’?如何在项目中灵活地应用‘开闭原则’,以避免在追求扩展性的同时影响到代码的可读性?”等等这些问题,都比较难掌握。

最有用

之所以说这条原则最有用,那是因为,扩展性是代码质量最重要的衡量标准之一。在 23 种经典设计模式中,大部分设计模式都是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。

OCP示例

API告警系统

现在API监控告警系统--Alert类负责,有不同的告警策略(比如超时间超过某个阈值,就发送通知)和不同的告警方式(如发送短信、通知、邮件等)

public class Alert {
  private AlertRule rule;
  private Notification notification;

  public Alert(AlertRule rule, Notification notification) {
    this.rule = rule;
    this.notification = notification;
  }

  public void check(String api, long requestCount, long errorCount, long durationOfSeconds) {
    long tps = requestCount / durationOfSeconds;
    if (tps > rule.getMatchedRule(api).getMaxTps()) {
      notification.notify(NotificationEmergencyLevel.URGENCY, "...");
    }
    if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
      notification.notify(NotificationEmergencyLevel.SEVERE, "...");
    }
  }
}

要添加一个新的告警功能:当每秒超时请求数超过某值时就发送通知。当我们在代码上直接修改时就是这样:

public class Alert {
  // ...省略AlertRule/Notification属性和构造函数...
  
  // 改动一:添加参数timeoutCount
  public void check(String api, long requestCount, long errorCount, long timeoutCount, long durationOfSeconds) {
    long tps = requestCount / durationOfSeconds;
    if (tps > rule.getMatchedRule(api).getMaxTps()) {
      notification.notify(NotificationEmergencyLevel.URGENCY, "...");
    }
    if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
      notification.notify(NotificationEmergencyLevel.SEVERE, "...");
    }
    // 改动二:添加接口超时处理逻辑
    long timeoutTps = timeoutCount / durationOfSeconds;
    if (timeoutTps > rule.getMatchedRule(api).getMaxTimeoutTps()) {
      notification.notify(NotificationEmergencyLevel.URGENCY, "...");
    }
  }
}
  • 在原来check方法中直接修改、添加逻辑,有可能会误改到原逻辑
  • check方法增加了参数,可能会影响原来的单元测试

OCP进行优化


public class Alert {
  private List<AlertHandler> alertHandlers = new ArrayList<>();
  
  public void addAlertHandler(AlertHandler alertHandler) {
    this.alertHandlers.add(alertHandler);
  }

  public void check(ApiStatInfo apiStatInfo) {
    for (AlertHandler handler : alertHandlers) {
      handler.check(apiStatInfo);
    }
  }
}

public class ApiStatInfo {//省略constructor/getter/setter方法
  private String api;
  private long requestCount;
  private long errorCount;
  private long durationOfSeconds;
}

public abstract class AlertHandler {
  protected AlertRule rule;
  protected Notification notification;
  public AlertHandler(AlertRule rule, Notification notification) {
    this.rule = rule;
    this.notification = notification;
  }
  public abstract void check(ApiStatInfo apiStatInfo);
}

public class TpsAlertHandler extends AlertHandler {
  public TpsAlertHandler(AlertRule rule, Notification notification) {
    super(rule, notification);
  }

  @Override
  public void check(ApiStatInfo apiStatInfo) {
    long tps = apiStatInfo.getRequestCount()/ apiStatInfo.getDurationOfSeconds();
    if (tps > rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()) {
      notification.notify(NotificationEmergencyLevel.URGENCY, "...");
    }
  }
}

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, "...");
    }
  }
}


public class ApplicationContext {
  private AlertRule alertRule;
  private Notification notification;
  private Alert alert;
  
  public void initializeBeans() {
    alertRule = new AlertRule(/*.省略参数.*/); //省略一些初始化代码
    notification = new Notification(/*.省略参数.*/); //省略一些初始化代码
    alert = new Alert();
    alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));
    alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));
  }
  public Alert getAlert() { return alert; }

  // 饿汉式单例
  private static final ApplicationContext instance = new ApplicationContext();
  private ApplicationContext() {
    initializeBeans();
  }
  public static ApplicationContext getInstance() {
    return instance;
  }
}

public class Demo {
  public static void main(String[] args) {
    ApiStatInfo apiStatInfo = new ApiStatInfo();
    // ...省略设置apiStatInfo数据值的代码
    ApplicationContext.getInstance().getAlert().check(apiStatInfo);
  }
}
  1. 增加Handler类,这样新的告警策略和原来的就会隔离开,互不影响。并且单元测试的话无需再尝试check方法,只测试不同Handler即可
  2. 将check方法的参数整合为一个APIStatInfo类,再配合Handler这种面向接口而非实现的编程技巧,使得check方法在后序开发中改动次数变少

应用OCP

深入理解OCP

OCP原则其实还是带有一定的主观性,比如

  • 不是所有修改都不允许
    • 有些不得做的修改,比如上面例子中initializeBeans中添加新的handler部分
  • 也不是完全不允许不符合OCP的代码,OCP是有成本的,通常会牺牲一部分代码的可读性
    • 比如对于逻辑本来就很简单的业务,初期完全没必要运用OCP提高代码可扩展性
  • 要牢记OCP的初衷,权衡取舍是否要使用抽象、多态等技巧实现OCP原则

如何在项目中灵活应用OCP

OCP的主要目的是提高代码的扩展性,灵活应用OCP的关键在于找到代码的可扩展点,进而,这要求设计者要对业务要很熟悉才行,能了解业务的发展方向,总体概况,甚至了解同行业的发展状况

另外,要有充足的理论知识,抽象、封装、多态、依赖注入、面向接口的编程思想以及各种设计模式

再举一个例子:

比如,我们代码中通过 Kafka 来发送异步消息。对于这样一个功能的开发,我们要学会将其抽象成一组跟具体消息队列(Kafka)无关的异步消息接口。�所有上层系统都依赖这组抽象的接口编程,并且通过依赖注入的方式来调用。当我们要替换新的消息队列的时候,比如将 Kafka 替换成 RocketMQ,可以很方便地拔掉老的消息队列实现,插入新的消息队列实现。具体代码如下所示:

// 这一部分体现了抽象意识
public interface MessageQueue { //... }
public class KafkaMessageQueue implements MessageQueue { //... }
public class RocketMQMessageQueue implements MessageQueue {//...}

public interface MessageFromatter { //... }
public class JsonMessageFromatter implements MessageFromatter {//...}
public class ProtoBufMessageFromatter implements MessageFromatter {//...}

public class Demo {
  private MessageQueue msgQueue; // 基于接口而非实现编程
  public Demo(MessageQueue msgQueue) { // 依赖注入
    this.msgQueue = msgQueue;
  }
  
  // msgFormatter:多态、依赖注入
  public void sendNotification(Notification notification, MessageFormatter msgFormatter) {
    //...    
  }
}