• 设计模式简记-设计原则之依赖反转原则


    3.5 依赖反转原则

    3.5.1 控制反转(IOC)

    • Inversion Of Control,缩写为 IOC
    public abstract class TestCase {
      public void run() {
        if (doTest()) {
          System.out.println("Test succeed.");
        } else {
          System.out.println("Test failed.");
        }
      }
      
      public abstract boolean doTest();
    }
    
    public class JunitApplication {
      private static final List<TestCase> testCases = new ArrayList<>();
      
      public static void register(TestCase testCase) {
        testCases.add(testCase);
      }
      
      public static final void main(String[] args) {
        for (TestCase case: testCases) {
          case.run();
        }
      }
    

    把这个简化版本的测试框架引入到工程中之后,只需要在框架预留的扩展点,也就是 TestCase 类中的 doTest() 抽象函数中,填充具体的测试代码就可以实现之前的功能了,完全不需要写负责执行流程的 main() 函数了。 具体的代码如下所示:

    public class UserServiceTest extends TestCase {
      @Override
      public boolean doTest() {
        // ... 
      }
    }
    
    // 注册操作还可以通过配置的方式来实现,不需要程序员显示调用register()
    JunitApplication.register(new UserServiceTest();
    
    • 这就是典型的通过框架来实现“控制反转”的例子:框架提供了一个可扩展的代码骨架,用来组装对象、管理整个执行流程。程序员利用框架进行开发的时候,只需要往预留的扩展点上,添加跟自己业务相关的代码,就可以利用框架来驱动整个程序流程的执行。

    • “控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程可以通过框架来控制。流程的控制权从程序员“反转”到了框架。

    • 控制反转并不是一种具体的实现技巧,而是一个比较笼统的设计思想,一般用来指导框架层面的设计。

    3.5.2 依赖注入(DI)

    • Dependency Injection,缩写为 DI

    • 跟控制反转恰恰相反,它是一种具体的编码技巧

    • 一句话来概括:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。

    • 例子:Notification 类负责消息推送,依赖 MessageSender 类实现推送商品促销、验证码等消息给用户。分别用依赖注入和非依赖注入两种方式来实现一下。具体的实现代码如下所示:

      // 非依赖注入实现方式
      public class Notification {
        private MessageSender messageSender;
        
        public Notification() {
          this.messageSender = new MessageSender(); //此处有点像hardcode
        }
        
        public void sendMessage(String cellphone, String message) {
          //...省略校验逻辑等...
          this.messageSender.send(cellphone, message);
        }
      }
      
      public class MessageSender {
        public void send(String cellphone, String message) {
          //....
        }
      }
      // 使用Notification
      Notification notification = new Notification();
      
      // 依赖注入的实现方式
      public class Notification {
        private MessageSender messageSender;
        
        // 通过构造函数将messageSender传递进来
        public Notification(MessageSender messageSender) {
          this.messageSender = messageSender;
        }
        
        public void sendMessage(String cellphone, String message) {
          //...省略校验逻辑等...
          this.messageSender.send(cellphone, message);
        }
      }
      //使用Notification
      MessageSender messageSender = new MessageSender();
      Notification notification = new Notification(messageSender);
      

      优化:

      public class Notification {
        private MessageSender messageSender;
        
        public Notification(MessageSender messageSender) {
          this.messageSender = messageSender;
        }
        
        public void sendMessage(String cellphone, String message) {
          this.messageSender.send(cellphone, message);
        }
      }
      
      public interface MessageSender {
        void send(String cellphone, String message);
      }
      
      // 短信发送类
      public class SmsSender implements MessageSender {
        @Override
        public void send(String cellphone, String message) {
          //....
        }
      }
      
      // 站内信发送类
      public class InboxSender implements MessageSender {
        @Override
        public void send(String cellphone, String message) {
          //....
        }
      }
      
      //使用Notification
      MessageSender messageSender = new SmsSender();
      Notification notification = new Notification(messageSender);
      

      以上,通过依赖注入的方式来将依赖的类对象传递进来,可以灵活地替换依赖的类,提高了代码的扩展性

    3.5.3 依赖注入框架(DI Framework)

    • 依赖注入框架有很多,比如 Google Guice、Java Spring、Pico Container、Butterfly Container 等。
    • 只需要通过依赖注入框架提供的扩展点,简单配置一下所有需要创建的类对象、类与类之间的依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。

    3.5.4 依赖反转原则(DIP)

    • Dependency Inversion Principle,缩写为 DIP

    • 高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。

    • 抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。

    • 高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。

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

    3.5.5 评论

    • SmallFly:

      依赖倒置原则概念是高层次模块不依赖于低层次模块。看似在要求高层次模块,实际上是在规范低层次模块的设计。

      低层次模块提供的接口要足够的抽象、通用,在设计时需要考虑高层次模块的使用种类和场景

      明明是高层次模块要使用低层次模块,对低层次模块有依赖性。现在反而低层次模块需要根据高层次模块来设计,出现了「倒置」的显现

      这样设计好处有两点:

      1. 低层次模块更加通用,适用性更广
      2. 高层次模块没有依赖低层次模块的具体实现,方便低层次模块的替换
  • 相关阅读:
    HOJ 2139 Spiderman's workout(动态规划)
    FZU 2107 Hua Rong Dao(dfs)
    Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
    Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
    Java 第十一届 蓝桥杯 省模拟赛 计算机存储中有多少字节
    Java 第十一届 蓝桥杯 省模拟赛 合法括号序列
    Java 第十一届 蓝桥杯 省模拟赛 合法括号序列
    Java 第十一届 蓝桥杯 省模拟赛 合法括号序列
    Java 第十一届 蓝桥杯 省模拟赛 无向连通图最少包含多少条边
    Java 第十一届 蓝桥杯 省模拟赛 无向连通图最少包含多少条边
  • 原文地址:https://www.cnblogs.com/wod-Y/p/12746794.html
Copyright © 2020-2023  润新知