zoukankan      html  css  js  c++  java
  • 设计模式简记-设计原则之依赖反转原则

    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. 高层次模块没有依赖低层次模块的具体实现,方便低层次模块的替换
  • 相关阅读:
    洛谷P2516 [HAOI2010]最长公共子序列 动态规划 容斥原理
    [LeetCode]235. Lowest Common Ancestor of a Binary Search Tree
    [LeetCode]144. Binary Tree Preorder Traversal二叉树前序遍历
    [LeetCode]129. Sum Root to Leaf Numbers路径数字求和
    [leetcode]645. Set Mismatch
    [leetcode]110BalancedBinaryTree平衡二叉树
    [leetcode]199. Binary Tree Right Side View
    [LeetCode]116. Populating Next Right Pointers in Each Node
    [leetcode]720. Longest Word in Dictionary字典中最长的单词
    [LeetCode]690. Employee Importance员工重要信息
  • 原文地址:https://www.cnblogs.com/wod-Y/p/12746794.html
Copyright © 2011-2022 走看看