接续上一篇的笔记:【设计模式笔记03】:里氏代换原则和依赖倒置原则

四、 依赖倒置原则 (DIP) (续)

5. 代码示例:消息接收
  • 反面示例 (不遵循DIP)

    • 场景: Person 类需要接收并处理来自 Email 类的信息。
    • 代码实现:
      class Email {
          public String getInfo() {
              return "电子邮件信息: Hello, world.";
          }
      }
      
      class Person {
          public void receiveEmail(Email email) { // 参数类型是具体的Email类
              System.out.println(email.getInfo());
          }
      }
      
    • 问题分析:
      • Person 类的 receiveEmail 方法直接依赖了具体的 Email 类。
      • 如果现在需要接收微信(Weixin)、短信(SMS)等其他类型的信息,就必须在 Person 类中增加新的方法,如 receiveWeixin(Weixin weixin)
      • 每次增加新的信息类型,都需要修改 Person 类的源代码,这违反了开闭原则 (OCP)依赖倒置原则 (DIP)
  • 正面示例 (遵循DIP)

    • 重构思路: 引入一个抽象的 IReceivedMsg 接口,让 Person 类依赖此接口,而不是具体的实现类。
    • 依赖注入的三种方式:
      1. 接口依赖 (方法参数传递)

        • 代码实现:
          // 1. 定义抽象信息接口
          interface IReceivedMsg {
              public String getInfo();
          }
          
          // 2. Person类依赖接口
          class Person {
              public void receive(IReceivedMsg iReceivedMsg) { // 参数类型是抽象的接口
                  System.out.println(iReceivedMsg.getInfo());
              }
          }
          
          // 3. 具体信息类实现接口
          class Email implements IReceivedMsg {
              public String getInfo() {
                  return "电子邮件: Hello, world.";
              }
          }
          
          class Weixin implements IReceivedMsg {
              public String getInfo() {
                  return "微信: How are you?";
              }
          }
          
        • 优点: 代码依赖于抽象,当需要扩展支持新的消息类型(如短信)时,只需增加一个新的类实现 IReceivedMsg 接口即可,Person 类无需任何修改。
      2. 构造器依赖 (构造函数传递)

        • 代码实现:
          interface IMessage {
              public String getInfo();
          }
          
          class People {
              private IMessage iMessage; // 成员变量是接口类型 (关联)
          
              public People(IMessage iMessage) { // 通过构造器注入依赖
                  this.iMessage = iMessage;
              }
          
              public void receive() {
                  System.out.println(this.iMessage.getInfo());
              }
          }
          
          class QQMessage implements IMessage { ... }
          
      3. Setter依赖 (Setter方法传递)

        • 代码实现:
          interface IMessage {
              public String getInfo();
          }
          
          class People {
              private IMessage iMessage; // 成员变量是接口类型 (关联)
          
              public void setIMessage(IMessage iMessage) { // 通过Setter方法注入依赖
                  this.iMessage = iMessage;
              }
          
              public void receive() {
                  System.out.println(this.iMessage.getInfo());
              }
          }
          
          class QQMessage implements IMessage { ... }
          
  • UML类图示例:

    在这里插入图片描述

    UML类图展示了 Person 类(或People类)与 IMessage 接口的关联关系,以及多个具体消息类(如Email, Weixin, QQMessage)对 IMessage 接口的实现关系。

  • 原则的协同体现:

    • 新增接口的实现类,无需修改已有类,符合开闭原则 (OCP)
    • 如果将接口用抽象类代替,此时,子类可以替换父类(抽象类)被Person类使用,还将符合里氏代换原则 (LSP)
6. 图表示例回顾与分析

我们再回头看之前开闭原则中的图表示例。

在这里插入图片描述

重构后的CRM图表示例的UML类图。ChartDisplay 类聚合 AbstractChart 抽象类,PieChartBarChart 继承自 AbstractChart

  • 如何体现依赖倒置原则和里氏代换原则?

    1. 引入抽象图表类 AbstractChartChartDisplay 类不再依赖具体的 PieChartBarChart,而是针对抽象的 AbstractChart 类进行编程。这就是依赖倒置原则的体现。
    2. 通过 setChart 方法注入:客户端可以通过 setChart(AbstractChart chart) 方法将实例化的具体图表对象(如 new PieChart())设置到 ChartDisplay 中。
    3. 扩展性:当需要增加一种新的图表,如折线图 LineChart 时,我们只需要:
      • 创建一个 LineChart 类,并让它继承 AbstractChart
      • 在客户端代码中,向 ChartDisplay 对象注入一个 LineChart 实例即可。
      • 这个过程中,原有的 ChartDisplayAbstractChartPieChartBarChart 的源代码都无须修改
    4. LSP的体现:在调用 setChart 方法时,任何 AbstractChart 的子类(如PieChartBarChartLineChart)都可以作为参数传入,并且ChartDisplay的行为是正确的、符合预期的。这就是里氏代换原则的体现。
  • 关于配置文件与源码修改

    • 在实际软件开发中,具体使用哪个实现类,通常是通过XML、Properties等配置文件来决定的。
    • 因为配置文件是纯文本文件,可以通过编辑器直接编辑,且无须重新编译。因此,一般不把对配置文件的修改认为是-对系统源代码的修改
    • 如果一个系统在扩展时只涉及到修改配置文件,而原有的程序代码没有任何修改,则可以认为是一个符合开闭原则的系统。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐