什么是依赖倒置原则?为什么它很重要?


当前回答

依赖倒置:依赖抽象,而不是具体。

控制反转:主要与抽象,以及主要如何成为系统的粘合剂。

以下是一些关于这方面的好文章:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

其他回答

依赖倒置:依赖抽象,而不是具体。

控制反转:主要与抽象,以及主要如何成为系统的粘合剂。

以下是一些关于这方面的好文章:

https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/

https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/

https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/

控制反转(IoC)是一种设计模式,在这种模式下,对象通过外部框架获得其依赖项,而不是向框架请求其依赖项。

使用传统查找的伪代码示例:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

使用IoC的类似代码:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

国际奥委会的好处是:

您不依赖于中心 框架,所以这可以改变如果 想要的。 因为对象是被创建的 以注射方式使用为佳 接口,很容易创建单元 替换依赖项的测试 模拟版本。 解耦代码。

依赖倒置的重点是制作可重用的软件。

其思想是,两段代码不再相互依赖,而是依赖于一些抽象的接口。然后你可以在没有另一块的情况下重复使用其中的任何一块。

最常见的实现方式是通过控制反转(IoC)容器,如Java中的Spring。在这个模型中,对象的属性是通过XML配置来设置的,而不是由对象自己去寻找它们的依赖项。

想象一下这个伪代码……

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass直接依赖于Service类和ServiceLocator类。如果你想在另一个应用程序中使用它,这两个都需要。现在想象一下……

public class MyClass
{
  public IService myService;
}

现在,MyClass依赖于一个单独的接口,IService接口。我们让IoC容器实际设置那个变量的值。

所以现在,MyClass可以很容易地在其他项目中重用,而不会带来其他两个类的依赖关系。

更好的是,您不必拖动MyService的依赖项,以及这些依赖项的依赖项,以及…好吧,你懂的。

这是什么?

The books Agile Software Development, Principles, Patterns, and Practices and Agile Principles, Patterns, and Practices in C# are the best resources for fully understanding the original goals and motivations behind the Dependency Inversion Principle. The article "The Dependency Inversion Principle" is also a good resource, but due to the fact that it is a condensed version of a draft which eventually made its way into the previously mentioned books, it leaves out some important discussion on the concept of a package and interface ownership which are key to distinguishing this principle from the more general advise to "program to an interface, not an implementation" found within the book Design Patterns (Gamma, et. al).

To provide a summary, the Dependency Inversion Principle is primarily about reversing the conventional direction of dependencies from "higher level" components to "lower level" components such that "lower level" components are dependent upon the interfaces owned by the "higher level" components. (Note: "higher level" component here refers to the component requiring external dependencies/services, not necessarily its conceptual position within a layered architecture.) In doing so, coupling isn't reduced so much as it is shifted from components that are theoretically less valuable to components which are theoretically more valuable.

这是通过设计其外部依赖关系用接口表示的组件来实现的,该接口的实现必须由组件的使用者提供。换句话说,定义的接口表示组件需要什么,而不是如何使用组件。“INeedSomething”,而不是“IDoSomething”)。

依赖倒置原则没有提到的是通过使用接口(例如MyService→[ILogger⇐Logger])抽象依赖关系的简单实践。虽然这将组件从依赖项的特定实现细节中解耦,但它并没有反转使用者和依赖项之间的关系(例如[MyService→IMyServiceLogger]⇐Logger。

为什么它很重要?

依赖倒置原则的重要性可以归结为一个单一的目标,即能够重用依赖于外部依赖的软件组件来实现部分功能(日志记录、验证等)。

在这个重用的总体目标中,我们可以划分出两种重用子类型:

Using a software component within multiple applications with sub-dependency implementations (e.g. You've developed a DI container and want to provide logging, but don't want to couple your container to a specific logger such that everyone that uses your container has to also use your chosen logging library). Using software components within an evolving context (e.g. You've developed business-logic components which remain the same across multiple versions of an application where the implementation details are evolving).

With the first case of reusing components across multiple applications, such as with an infrastructure library, the goal is to provide a core infrastructure need to your consumers without coupling your consumers to sub-dependencies of your own library since coupling to such dependencies requires your consumers to require the same dependencies as well. This can be problematic when consumers of your library choose to use a different library for the same infrastructure needs (e.g. NLog vs. log4net), or if they choose to use a later version of the required library which isn't backward compatible with the version required by your library.

对于重用业务逻辑组件的第二种情况(即。“高级组件”),目标是将应用程序的核心域实现与实现细节的不断变化的需求(即更改/升级持久性库、消息传递库、加密策略等)隔离开来。理想情况下,更改应用程序的实现细节不应该破坏封装应用程序业务逻辑的组件。

注意:有些人可能反对将第二种情况描述为实际的重用,认为在单个演进应用程序中使用的业务逻辑组件等组件只代表单一用途。然而,这里的思想是,对应用程序实现细节的每次更改都呈现一个新的上下文,因此呈现一个不同的用例,尽管最终目标可以区分为隔离和可移植性。

While following the Dependency Inversion Principle in this second case can offer some benefit, it should be noted that its value as applied to modern languages such as Java and C# is much reduced, perhaps to the point of being irrelevant. As discussed earlier, the DIP involves separating implementation details into separate packages completely. In the case of an evolving application, however, simply utilizing interfaces defined in terms of the business domain will guard against needing to modify higher-level components due to changing needs of implementation detail components, even if the implementation details ultimately reside within the same package. This portion of the principle reflects aspects that were pertinent to the language in view when the principle was codified (i.e. C++) which aren't relevant to newer languages. That said, the importance of the Dependency Inversion Principle primarily lies with the development of reusable software components/libraries.

在这里可以找到关于这个原则的更长的讨论,因为它与接口的简单使用、依赖注入和分离接口模式有关。此外,关于该原则如何与动态类型语言(如JavaScript)相关的讨论可以在这里找到。

依赖倒置的良好应用在应用程序的整个体系结构层面上提供了灵活性和稳定性。它将允许您的应用程序更安全、更稳定地发展。

传统分层建筑

传统上,分层架构UI依赖于业务层,而业务层又依赖于数据访问层。

您必须了解层、包或库。让我们看看代码是怎样的。

我们将有一个用于数据访问层的库或包。

// DataAccessLayer.dll
public class ProductDAO {

}

以及依赖于数据访问层的另一个库或包层业务逻辑。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

带有依赖倒置的分层体系结构

依赖倒置表明:

高级模块不应该依赖于低级模块。两者都应该依赖于抽象。

抽象不应该依赖于细节。细节应该依赖于抽象。

什么是高级模块和低级模块?思考模块,如库或包,高级模块将是那些传统上具有依赖关系的模块,而它们所依赖的低级模块。

换句话说,模块高层是调用操作的地方,而模块低层是执行操作的地方。

从这一原则可以得出一个合理的结论,即具象之间不应存在依赖关系,而必须对抽象存在依赖关系。但根据我们所采取的方法,我们可能会误用投资依赖依赖,而是一种抽象。

假设我们对代码进行如下调整:

我们将为定义抽象的数据访问层提供一个库或包。

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

以及依赖于数据访问层的另一个库或包层业务逻辑。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

尽管我们依赖于抽象,但业务和数据访问之间的依赖关系保持不变。

要获得依赖项反转,持久性接口必须定义在高级逻辑或域所在的模块或包中,而不是定义在低级模块中。

首先定义什么是领域层,其通信的抽象是定义持久化。

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

在持久化层依赖于域之后,如果定义了依赖项,就可以进行反转。

// Persistence.dll
public class ProductDAO : IProductRepository{

}

(来源:xurxodev.com)

深化原则

重要的是要吸收好概念,深化目的和效益。如果我们机械地停留在学习典型案例存储库中,我们将无法确定在哪里可以应用依赖原则。

但是为什么要反转依赖关系呢?除了具体的例子,主要目标是什么?

这通常允许最稳定的东西,不依赖于不太稳定的东西,更频繁地变化。

持久化类型的更改(访问相同数据库的数据库或技术)比设计用于与持久化通信的域逻辑或操作更容易。因此,这种依赖是相反的,因为如果发生这种更改,则更容易更改持久性。这样我们就不需要改变定义域了。域层是所有层中最稳定的,这就是为什么它不应该依赖于任何东西。

但不只是这个存储库示例。有许多应用此原则的场景,并且有基于此原则的架构。

体系结构

在某些架构中,依赖倒置是其定义的关键。在所有的域中,它是最重要的,它是用来指示域和其他被定义的包或库之间的通信协议的抽象。

干净的建筑

在Clean体系结构中,域位于中心,如果您查看指示依赖关系的箭头方向,就可以清楚地看到哪些是最重要和最稳定的层。外层被认为是不稳定的工具,因此避免依赖它们。

(来源:8 thlight.com)

六角结构

这与六边形体系结构发生的情况相同,其中域也位于中心部分,端口是从多米诺骨牌向外通信的抽象。这里很明显,域是最稳定的,传统的依赖关系是颠倒的。

(来源:pragprog.com)