什么是依赖倒置原则?为什么它很重要?
当前回答
这是什么?
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)相关的讨论可以在这里找到。
其他回答
查看这个文档:依赖倒置原则。
它基本上是说:
高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 抽象永远不应该依赖于细节。细节应该依赖于抽象。
至于为什么它很重要,简而言之:更改是有风险的,通过依赖于概念而不是实现,您减少了在调用站点更改的需求。
DIP有效地减少了不同代码段之间的耦合。其思想是,尽管有许多实现方法,比如日志记录工具,但使用它的方式应该在时间上相对稳定。如果您可以提取一个表示日志记录概念的接口,那么这个接口在时间上应该比它的实现稳定得多,并且调用站点在维护或扩展日志记录机制时受更改的影响应该小得多。
通过使实现依赖于接口,您可以在运行时选择哪个实现更适合您的特定环境。根据具体情况,这可能也很有趣。
基本上它说:
类应该依赖于抽象(例如接口,抽象类),而不是特定的细节(实现)。
依赖倒置原则(DIP)就是这么说的
i)高级模块不应该依赖于低级模块。两者都应该依赖于抽象。
ii)抽象永远不应该依赖于细节。细节应该依赖于抽象。
例子:
public interface ICustomer
{
string GetCustomerNameById(int id);
}
public class Customer : ICustomer
{
//ctor
public Customer(){}
public string GetCustomerNameById(int id)
{
return "Dummy Customer Name";
}
}
public class CustomerFactory
{
public static ICustomer GetCustomerData()
{
return new Customer();
}
}
public class CustomerBLL
{
ICustomer _customer;
public CustomerBLL()
{
_customer = CustomerFactory.GetCustomerData();
}
public string GetCustomerNameById(int id)
{
return _customer.GetCustomerNameById(id);
}
}
public class Program
{
static void Main()
{
CustomerBLL customerBLL = new CustomerBLL();
int customerId = 25;
string customerName = customerBLL.GetCustomerNameById(customerId);
Console.WriteLine(customerName);
Console.ReadKey();
}
}
注意:类应该依赖于抽象,如接口或抽象类,而不是特定的细节(接口的实现)。
依赖倒置的重点是制作可重用的软件。
其思想是,两段代码不再相互依赖,而是依赖于一些抽象的接口。然后你可以在没有另一块的情况下重复使用其中的任何一块。
最常见的实现方式是通过控制反转(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的依赖项,以及这些依赖项的依赖项,以及…好吧,你懂的。
当我们设计软件应用程序时,我们可以考虑低层类——实现基本和主要操作的类(磁盘访问、网络协议……)和高层类——封装复杂逻辑的类(业务流……)。
最后一个依赖于低级类。实现这种结构的自然方法是编写低级类,一旦我们有了它们,就编写复杂的高级类。由于高级类是根据其他类定义的,这似乎是一种合乎逻辑的方法。但这不是一个灵活的设计。如果我们需要替换一个低级类,会发生什么?
依赖倒置原则指出:
高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 抽象不应该依赖于细节。细节应该依赖于抽象。
这个原则试图“颠倒”传统的概念,即软件中的高级模块应该依赖于低级模块。在这里,高级模块拥有由低级模块实现的抽象(例如,决定接口的方法)。因此,较低级别的模块依赖于较高级别的模块。