依赖倒置原则(适用于 Java)

Dependency Inversion Principle (as it applies to Java)

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

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

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

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

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

这是正确的解释吗?

这可能会澄清一些事情:

Explanation of the UML arrows

请注意,这些图像是针对 Visual Studio 的,但我认为 大多数 的信息与大多数 UML 文档相同。

The dashed line represents the equivalent of "implements"

UML 中实体的命名也让我有点困惑...看来 Policy 依赖于描述与 Mechanism 的较低级别模块的合同的接口

但是在第二条(实线)上应该代表继承(至少我相信)。 Mechanism 实现接口(抽象)Policy 而不是在 Policy 引用具体 Mechanism 时应用 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();

这当然会打印到控制台 "Something important"。现在,如果您不想登录到控制台,而是登录到数据库,会发生什么?

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!漂亮的 neato IMO。

请注意,在上面的示例中,我正在构建对象并提供实现,这也可以使用像 Java 的 Spring.

这样的 IOC 框架来完成

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

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

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