什么是依赖倒置原则?为什么它很重要?
当前回答
依赖倒置:依赖抽象,而不是具体。
控制反转:主要与抽象,以及主要如何成为系统的粘合剂。
以下是一些关于这方面的好文章:
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/
其他回答
依赖倒置原则的一个更清晰的表述方式是:
封装复杂业务逻辑的模块不应该直接依赖于封装业务逻辑的其他模块。相反,它们应该只依赖于简单数据的接口。
也就是说,不是像人们通常做的那样实现你的类逻辑:
class Dependency { ... }
class Logic {
private Dependency dep;
int doSomething() {
// Business logic using dep here
}
}
你应该这样做:
class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
private Dependency dep;
...
}
class Logic {
int doSomething(Data data) {
// compute something with data
}
}
Data和DataFromDependency应该与Logic在同一个模块中,而不是与Dependency在一起。
为什么要这么做?
这两个业务逻辑模块现在已解耦。当Dependency改变时,你不需要改变Logic。 理解Logic所做的是一个简单得多的任务:它只对看起来像ADT的东西起作用。 现在可以更容易地检验逻辑。现在,您可以直接实例化假数据Data并将其传入。不需要模拟或复杂的测试脚手架。
查看这个文档:依赖倒置原则。
它基本上是说:
高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 抽象永远不应该依赖于细节。细节应该依赖于抽象。
至于为什么它很重要,简而言之:更改是有风险的,通过依赖于概念而不是实现,您减少了在调用站点更改的需求。
DIP有效地减少了不同代码段之间的耦合。其思想是,尽管有许多实现方法,比如日志记录工具,但使用它的方式应该在时间上相对稳定。如果您可以提取一个表示日志记录概念的接口,那么这个接口在时间上应该比它的实现稳定得多,并且调用站点在维护或扩展日志记录机制时受更改的影响应该小得多。
通过使实现依赖于接口,您可以在运行时选择哪个实现更适合您的特定环境。根据具体情况,这可能也很有趣。
依赖倒置的重点是制作可重用的软件。
其思想是,两段代码不再相互依赖,而是依赖于一些抽象的接口。然后你可以在没有另一块的情况下重复使用其中的任何一块。
最常见的实现方式是通过控制反转(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的依赖项,以及这些依赖项的依赖项,以及…好吧,你懂的。
当我们设计软件应用程序时,我们可以考虑低层类——实现基本和主要操作的类(磁盘访问、网络协议……)和高层类——封装复杂逻辑的类(业务流……)。
最后一个依赖于低级类。实现这种结构的自然方法是编写低级类,一旦我们有了它们,就编写复杂的高级类。由于高级类是根据其他类定义的,这似乎是一种合乎逻辑的方法。但这不是一个灵活的设计。如果我们需要替换一个低级类,会发生什么?
依赖倒置原则指出:
高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 抽象不应该依赖于细节。细节应该依赖于抽象。
这个原则试图“颠倒”传统的概念,即软件中的高级模块应该依赖于低级模块。在这里,高级模块拥有由低级模块实现的抽象(例如,决定接口的方法)。因此,较低级别的模块依赖于较高级别的模块。
基本上它说:
类应该依赖于抽象(例如接口,抽象类),而不是特定的细节(实现)。