不理解它别说你会面向对象

如何做到 “对扩展开放、修改关闭”?

在之前的推文中,我们通过引入一组 handler 的方式来实现支持开闭原则。如果你没有太多复杂代码的设计和开发经验,你可能会有这样的疑问:

这样的代码设计思路我怎么想不到呢?

实际上,开闭原则讲的就是代码的扩展性问题,是判断一段代码是否易扩展的 “金标准”。如果某段代码在应对未来需求变化的时候,能够做到 “对扩展开放、对修改关闭”,那就说明这段代码的扩展性比较好。所以,问如何才能做到 “对扩展开放、对修改关闭”,也就粗略地等同于在问,如何才能写出扩展性好的代码。

在讲具体的方法论之前,我们先来看一些更加偏向顶层的指导思想。

  1. 为了尽量写出扩展性好的代码,我们要时刻具备扩展意识抽象意识封装意识。这些 “潜意识” 可能比任何开发技巧都重要。

  2. 在写代码的时候后,我们要多花点时间往前多思考一下,这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到 “对扩展开放、对修改关闭”。

  3. 还有,在识别出代码可变部分和不可变部分之后,我们要将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。

支持开闭原则的一些更加具体的方法论

代码的扩展性是代码质量评判的最重要的标准之一。特别是23种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的。

在众多的设计原则、思想、模式中,最常用来提高代码扩展性的方法有:

  1. 多态
  2. 依赖注入、
  3. 基于接口而非实现编程,
  4. 以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)。

如何利用多态、依赖注入、基于接口而非实现编程,来实现 “对扩展开放、对修改关闭”

实际上,多态、依赖注入、基于接口而非实现编程,以及前面提到的抽象意识,说的都是同一种设计思路,只是从不同的角度、不同的层面来阐述而已。这也体现了 “很多设计原则、思想、模式都是相通的” 这一思想。

接下来,我就通过一个例子来解释一下,如何利用这几个设计思想或原则来实现 “对扩展开放、对修改关闭”。如果你对这块不了解,可以暂时先忽略这个概念,只关注多态、基于接口而非实现编程以及抽象意识。

// 这一部分体现了抽象意识

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) {

    //...

  }

}

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

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们