什么是依赖倒置原则?为什么它很重要?
当前回答
这是什么?
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)就是这么说的
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)是一种设计模式,在这种模式下,对象通过外部框架获得其依赖项,而不是向框架请求其依赖项。
使用传统查找的伪代码示例:
class Service {
Database database;
init() {
database = FrameworkSingleton.getService("database");
}
}
使用IoC的类似代码:
class Service {
Database database;
init(database) {
this.database = database;
}
}
国际奥委会的好处是:
您不依赖于中心 框架,所以这可以改变如果 想要的。 因为对象是被创建的 以注射方式使用为佳 接口,很容易创建单元 替换依赖项的测试 模拟版本。 解耦代码。
我可以在上面的回答中看到很好的解释。然而,我想用简单的例子提供一些简单的解释。
依赖倒置原则允许程序员删除硬编码的依赖项,这样应用程序就变得松散耦合和可扩展。
如何实现这一点:通过抽象
没有依赖反转:
class Student {
private Address address;
public Student() {
this.address = new Address();
}
}
class Address{
private String perminentAddress;
private String currentAdrress;
public Address() {
}
}
在上面的代码片段中,地址对象是硬编码的。相反,如果我们可以使用依赖倒置,并通过传递构造函数或setter方法注入地址对象。让我们来看看。
使用依赖倒置:
class Student{
private Address address;
public Student(Address address) {
this.address = address;
}
//or
public void setAddress(Address address) {
this.address = address;
}
}
除了一大堆不错的答案之外,我还想举一个我自己的小例子来说明好的和坏的做法。是的,我不是那种会扔石头的人!
比如说,你想要一个小程序通过控制台I/O将一个字符串转换成base64格式。这是一种幼稚的方法:
class Program
{
static void Main(string[] args)
{
/*
* BadEncoder: High-level class *contains* low-level I/O functionality.
* Hence, you'll have to fiddle with BadEncoder whenever you want to change
* the I/O mode or details. Not good. A good encoder should be I/O-agnostic --
* problems with I/O shouldn't break the encoder!
*/
BadEncoder.Run();
}
}
public static class BadEncoder
{
public static void Run()
{
Console.WriteLine(Convert.ToBase64String(Encoding.UTF8.GetBytes(Console.ReadLine())));
}
}
DIP基本上是说高级组件不应该依赖于低级实现,根据Robert C. Martin(《清洁架构》)的说法,“级别”是指与I/O的距离。但是如何摆脱这种困境呢?简单地让中央编码器只依赖于接口,而不考虑这些接口是如何实现的:
class Program
{
static void Main(string[] args)
{
/* Demo of the Dependency Inversion Principle (= "High-level functionality
* should not depend upon low-level implementations"):
* You can easily implement new I/O methods like
* ConsoleReader, ConsoleWriter without ever touching the high-level
* Encoder class!!!
*/
GoodEncoder.Run(new ConsoleReader(), new ConsoleWriter()); }
}
public static class GoodEncoder
{
public static void Run(IReadable input, IWriteable output)
{
output.WriteOutput(Convert.ToBase64String(Encoding.ASCII.GetBytes(input.ReadInput())));
}
}
public interface IReadable
{
string ReadInput();
}
public interface IWriteable
{
void WriteOutput(string txt);
}
public class ConsoleReader : IReadable
{
public string ReadInput()
{
return Console.ReadLine();
}
}
public class ConsoleWriter : IWriteable
{
public void WriteOutput(string txt)
{
Console.WriteLine(txt);
}
}
注意,你不需要触摸GoodEncoder来改变I/O模式——这个类对它知道的I/O接口很满意;任何IReadable和IWriteable的低级实现都不会打扰它。
推荐文章
- 什么是依赖倒置原则?为什么它很重要?
- 为什么在Python方法中需要显式地有“self”参数?
- 如何在方法中访问“静态”类变量?
- 为什么c#不提供c++风格的'friend'关键字?
- String, StringBuffer和StringBuilder
- 存储库和服务层的区别?
- DDD -实体不能直接访问存储库的规则
- 为什么STL如此严重地基于模板而不是继承?
- 如何在Objective-C中声明类级属性?
- 面向方面编程与面向对象编程
- c++中类似于java的instanceof
- 在python中遍历对象属性
- 将类代码分离为头文件和cpp文件
- 在PHP中使用getter和setter而不是函数或简单的公共字段有什么优点?
- 基于原型的继承与基于类的继承