【问题标题】:Dependency Inversion Principle (as it applies to Java)依赖倒置原则(适用于 Java)
【发布时间】:2015-04-21 16:39:30
【问题描述】:

我正在自学 S.O.L.I.D. 中涉及的原则。面向对象编程,但无法理解字母 D 中的所有细节(依赖倒置原则)。

我正在阅读它在 Wikipedia (http://en.wikipedia.org/wiki/Dependency_inversion_principle) 中的条目,但我不理解图中的所有内容:

http://en.wikipedia.org/wiki/Dependency_inversion_principle#/media/File:DIPLayersPattern_v2.png)

我注意到有两种不同类型的箭头 - 一种是虚线的,一种是实线的。

根据我目前的理解,虚线表示相当于Java中的“implements”关键字,实线表示关键字“extends”。

这是正确的解释吗?

【问题讨论】:

    标签: java oop solid-principles dependency-inversion


    【解决方案1】:

    这可能会澄清一些事情:

    Explanation of the UML arrows

    请注意,这些图像适用于 Visual Studio,但我认为大部分信息与大多数 UML 文档相同。

    虚线表示“实现”的等价物

    UML 中实体的命名也让我有点吃惊……看来Policy 依赖于一个接口,该接口描述了与Mechanism 的较低级别模块的合同

    但是在第二条(实线)上应该代表继承(至少我相信)。当Policy引用具体的Mechanism时,Mechanism实现了接口(抽象)Policy而不是在应用DIP之前。

    它试图传达的主要是类不应该依赖于其他类,但是它们可以依赖于抽象(接口)而不是具体。

    最简单的例子:原始 Foo/Logger 依赖于较低级别的模块。

    // "Low level Module" Mechanism equivilant
    public class Logger {
        public void logInformation(String logInfo) {
            System.out.println(logInfo);
        }
    }
    
    // "High level module" Policy equivalent.
    public class Foo {
        // direct dependency of a low level module.
        private Logger logger = new Logger();
    
        public void doStuff() {
            logger.logInformation("Something important.");
        }
    }
    

    上面Foo依赖于Logger的具体实现。这可以这样重构(注意有几种方法可以做到这一点,这只是一种)

    public interface ILogger {
        void logInformation(String logInfo);
    }
    
    public class Logger implements ILogger {
        @Override
        public void logInformation(string logInfo) {
            System.out.println(logInfo);
        }
    }
    
    public class Foo {
        private ILogger logger;
        public void setLoggerImpl(ILogger loggerImpl) {
            this.logger = loggerImpl;
        }
    
        public void doStuff() {
            logger.logInformation("Something important.");
        }
    }
    

    在这个重构中,Foo 不再依赖于Logger,而是使用了ILogger 接口——这意味着您可以在运行时切换 ILogger 的实现、对象实例化等。

    你可以这样消费Foo

    Foo foo = new Foo();
    ILogger logger = new Logger();
    foo.setLoggerImpl(logger);
    foo.doStuff();
    

    这当然会打印到控制台“重要的事情”。现在,如果您不想登录到控制台,而是登录到数据库,会发生什么?

    public class LoggerToDb implements ILogger {
        @Override
        public void logInformation(string logInfo) {
            DbContext databaseContext = new DbContext();
            databaseContext.insertLog(logInfo);
        }
    }
    

    现在可以这样消费:

    Foo foo = new Foo();
    ILogger logger = new LoggerToDb();
    foo.setLoggerImpl(logger);
    foo.doStuff();
    

    注意你的Foo 实现中什么都没有改变,因为Foo 不依赖于Logger,而是ILogger - 通过这种方法,我们可以为抽象提供一个新的具体化,并交换它进入Foo,甚至没有碰过Foo!漂亮整洁的 IMO。

    请注意,在上面的示例中,我正在构建对象并提供实现,这也可以使用 Java 的 Spring 等 IOC 框架来完成。

    【讨论】:

    • @LuiggiMendoza 谢谢,永远不会得到正确的语法:)
    • 我认为你混淆了依赖注入和依赖倒置。 DIP 适用于模块间的依赖关系,是对标准分层架构的改进。重点是模块之间的关系。 More info here
    • @plalx hmm... 我的印象是 DI 是完成 DIP 的一种方法 - 我没有在我的示例中特别指出它(因为它在 wiki 文章 OP 引用),但 ILoggerFoo“拥有”,而不是 LoggerILogger 的任何其他实现。从我读到的确实描述了 DIP ......你能澄清一下遗漏了什么吗?
    • @Kriten,DI是一种实现控制反转的方法,与依赖反转不同。 DI 是实现 DIP 的重要组成部分,但 DIP 是一个原则,简单地说: 1. 高级模块不依赖于低级模块,而是依赖于抽象。 2. 抽象不应该依赖于细节。细节应该取决于抽象。
    【解决方案2】:

    虚线表示源代码依赖项。带空三角形的实线表示一种特殊的依赖类型:类继承或接口实现。

    在此图中,Policy Service 和 Mechanism Service 是抽象,Policy Service 是更高级别的抽象。机制和效用是细节。

    Policy 对 Mechanism 和 Utility 有明显的运行时依赖(从 Policy 开始的控制流会到达这些细节),而在较低级别上,Mechanism 对 Utility 有类似的运行时依赖。但是,在源码层面,Policy 只依赖于 Policy Service,而 Mechanism 也依赖于 Policy Service。这就是依赖倒置。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-09-09
      • 2013-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多