16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?

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

开闭原则的定义,说明了开闭原则并不是有绝对衡量标准的。对业务的修改,是否满足开闭原则,取决于你对业务颗粒度的理解。

平时写的非扩展代码例子,比如告警功能,check()中包含各种达到阈值即告警的if逻辑判断,如果后续继续加阈值条件的话,1:可能check方法的参数首先就要变了,不够用,此处修改了通用方法的签名,改动很大 2:修改逻辑要追加到check()方法内部,check()的单元测试方法也要相应修改,并且逻辑会越堆越多。具体示例代码如下:

  1. public class Alert {
  2. // ...省略AlertRule/Notification属性和构造函数...
  3. // 改动一:添加参数timeoutCount
  4. public void check(String api, long requestCount, long errorCount, long timeoutCount, long durationOfSeconds) {
  5. long tps = requestCount / durationOfSeconds;
  6. if (tps > rule.getMatchedRule(api).getMaxTps()) {
  7. notification.notify(NotificationEmergencyLevel.URGENCY, "...");
  8. }
  9. if (errorCount > rule.getMatchedRule(api).getMaxErrorCount()) {
  10. notification.notify(NotificationEmergencyLevel.SEVERE, "...");
  11. }
  12. // 改动二:添加接口超时处理逻辑
  13. long timeoutTps = timeoutCount / durationOfSeconds;
  14. if (timeoutTps > rule.getMatchedRule(api).getMaxTimeoutTps()) {
  15. notification.notify(NotificationEmergencyLevel.URGENCY, "...");
  16. }
  17. }
  18. }

修改后得代码如下:引入handler的概念,一个handler对应一个告警规则(新增类的方式实现扩展)。check()方法的参数改成对象参数,避免修改影响到之前方法调用(参数类内部实现扩展)。

  1. public class Alert {
  2. private List<AlertHandler> alertHandlers = new ArrayList<>();
  3. public void addAlertHandler(AlertHandler alertHandler) {
  4. this.alertHandlers.add(alertHandler);
  5. }
  6. public void check(ApiStatInfo apiStatInfo) {
  7. for (AlertHandler handler : alertHandlers) {
  8. handler.check(apiStatInfo);
  9. }
  10. }
  11. }
  12. public class ApiStatInfo {//省略constructor/getter/setter方法
  13. private String api;
  14. private long requestCount;
  15. private long errorCount;
  16. private long durationOfSeconds;
  17. }
  18. public abstract class AlertHandler {
  19. protected AlertRule rule;
  20. protected Notification notification;
  21. public AlertHandler(AlertRule rule, Notification notification) {
  22. this.rule = rule;
  23. this.notification = notification;
  24. }
  25. public abstract void check(ApiStatInfo apiStatInfo);
  26. }
  27. public class TpsAlertHandler extends AlertHandler {
  28. public TpsAlertHandler(AlertRule rule, Notification notification) {
  29. super(rule, notification);
  30. }
  31. @Override
  32. public void check(ApiStatInfo apiStatInfo) {
  33. long tps = apiStatInfo.getRequestCount()/ apiStatInfo.getDurationOfSeconds();
  34. if (tps > rule.getMatchedRule(apiStatInfo.getApi()).getMaxTps()) {
  35. notification.notify(NotificationEmergencyLevel.URGENCY, "...");
  36. }
  37. }
  38. }
  39. public class ErrorAlertHandler extends AlertHandler {
  40. public ErrorAlertHandler(AlertRule rule, Notification notification){
  41. super(rule, notification);
  42. }
  43. @Override
  44. public void check(ApiStatInfo apiStatInfo) {
  45. if (apiStatInfo.getErrorCount() > rule.getMatchedRule(apiStatInfo.getApi()).getMaxErrorCount()) {
  46. notification.notify(NotificationEmergencyLevel.SEVERE, "...");
  47. }
  48. }
  49. }

建议调用方式:

  1. public class ApplicationContext {
  2. private AlertRule alertRule;
  3. private Notification notification;
  4. private Alert alert;
  5. public void initializeBeans() {
  6. alertRule = new AlertRule(/*.省略参数.*/); //省略一些初始化代码
  7. notification = new Notification(/*.省略参数.*/); //省略一些初始化代码
  8. alert = new Alert();
  9. alert.addAlertHandler(new TpsAlertHandler(alertRule, notification));
  10. alert.addAlertHandler(new ErrorAlertHandler(alertRule, notification));
  11. }
  12. public Alert getAlert() { return alert; }
  13. // 饿汉式单例
  14. private static final ApplicationContext instance = new ApplicationContext();
  15. private ApplicationContext() {
  16. initializeBeans();
  17. }
  18. public static ApplicationContext getInstance() {
  19. return instance;
  20. }
  21. }
  22. public class Demo {
  23. public static void main(String[] args) {
  24. ApiStatInfo apiStatInfo = new ApiStatInfo();
  25. // ...省略设置apiStatInfo数据值的代码
  26. ApplicationContext.getInstance().getAlert().check(apiStatInfo);
  27. }
  28. }
  1. 上述的例子并不绝对是优化后的更好,假如告警规则不多,后续也不会有新增的复杂逻辑,原先的写法会更好,代码量小、可读性强、开发成本也低。所以我们在做扩展的时候,需要考虑衡量点,对于短期、明确会扩展的做扩展设计,反之则等待下次驱动设计时候才做代码重构
  2. 识别出代码可变部分和不可变部分之后,我们要将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改(基于接口而非实现编程)。以对接Kafka为例,我们要将功能抽象成与Kafka无关的接口,接口设计如下:这样后续就算替换掉Kafka换成Rocket MQ也对上层应用无感知 ```java

// 这一部分体现了抽象意识 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) { //…
} } ```

19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?

SOLID原则中的D,依赖反转原则

控制反转(IOC)

此处不和Spring框架的IOC联系在一起。这里的”控制”是对程序流程的控制,而”反转”指的是没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员”反转”到了框架。
控制反转实现的方法有很多,除了类似模板方法设计模式之外,还有依赖注入等方法。
控制反转是一个比较笼统的设计思想,一般用来指导框架层面的设计。

依赖注入(DI)

依赖注入是不通过new()的方式在类内部创建依赖类对象,而是将依赖对象在外部创建好之后,通过构造函数、函数方法等方式传递(或注入)给类使用。
通过依赖注入方式,将依赖的类对象传递进来,这样就提高了代码的扩展性,我们可以灵活的替换依赖的类。另外节约了大量的类对象的创建和依赖的代码开发。

依赖注入框架(DI Framework)

对于我们自己来实现依赖注入的方式,很多时候不过是把手动new()移到更上层代码而已,还是需要程序员自己来实现。
这个时候我们就需要依赖注入框架。我们只需要通过依赖注入框架提供的扩展点,简单配置一下所有需要创建的类对象、类与类之间的依赖关系,就可以实现框架自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事。
现成的依赖注入框架有很多,比如Google Guice、Java Spring、Pico Container、Buttefly Container。Spring框架自称是控制反转容器,其实只是钟宽泛的描述,Spring框架的控制反转主要通过依赖注入来实现的。

依赖反转原则(DIP)

也称依赖倒置原则。高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来相互依赖。除此之外,抽象不要依赖具体细节,具体实现细节依赖抽象。
Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Servlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。