理解每个名称的细节并不完全重要,因为许多人最终会互换使用它们。 我们几乎不可能纠正此博客文章中的所有内容。
重要的是我们了解每个主题背后的概念和想法,并了解每个主题试图解决的架构问题。
今天,我们将专注于依赖倒置,并稍微了解一下控制倒置。
依赖倒置是根本
这一切都始于Bob叔叔在其1996年5月的C ++报告中的依赖依赖倒置原则中引入的这个简单概念。
如果您有时间阅读该文章,请帮个忙。 您不需要了解太多的C ++就可以理解它,它确实为Bob试图解决的问题提供了启示。
鲍勃在他的著作《 敏捷软件开发,原理,模式和实践以及C#中的敏捷原理,模式和实践》中谈到了这一原则。
这个原则实际上非常简单地陈述为:
- 高级模块不应依赖于低级模块。 两者都应依赖抽象。
- 抽象不应依赖细节。 细节应取决于抽象。
我认为这个概念很容易被误解和误用,因为我们经常忽略应用此原理的原因。
如果您熟悉IoC和依赖注入,则可以从依赖转换的定义中了解两者的基础。
依赖倒置解决了什么问题?
依赖倒置解决了高级模块依赖于低级模块的接口及其细节并与之耦合的问题。
让我给您一个当前问题的真实示例,该问题可以通过依赖反转来解决。
环顾您的房屋,并计算所有装有电池的设备,这些电池必须以某种方式充电。
像:
- 数码相机
- 手机
- 便携式摄录机
- 无线耳机
- 游戏控制器
所有这些东西有什么共同点? 他们没有共同的收费接口。 有些使用微型USB,有些使用微型USB,有些使用自己的时髦插头。
因此,您不可能只用一件事为所有设备充电。 每个设备都必须有不同的东西。 您家的“为移动设备充电”模块取决于设备。 如果您更换手机,则需要新的充电器。 如果升级相机,则需要新的充电器。
依赖关系走错了路。 低层设备定义了您的家庭用来充电的接口。 您家的充电能力应定义设备必须使用的接口。 依赖关系应反转。 它将使您的生活更加轻松。
让我们再看一个我敢打赌依赖反转的地方的例子。 现在,我不知道沃尔玛的IT结构,但是我敢冒险猜测,当他们从所有分销商那里收到发票时,发票将采用沃尔玛指定的文件格式,而不是相反。
我敢打赌,沃尔玛会为其从许多业务合作伙伴那里收到的所有数据指定架构。 让我们假设他们这样做。 在这种情况下,他们已经颠倒了依赖性,实际上他们已经颠倒了控制。 沃尔玛不是由供应商来控制其界面,而是通过其界面来控制供应商。
这意味着每当供应商更改其内部系统时,他们都必须仍然符合沃尔玛的界面,而不是沃尔玛必须进行更改以适应每个供应商对其格式的更改。
返回您的代码...
现在,让我们看一个代码示例,看看依赖反转如何帮助我们。 假设您正在创建一个高级模块,用于解析日志文件并将一些基本信息存储到数据库中。
在这种情况下,您希望能够处理来自多个不同来源的几个不同的日志文件,并将它们共享的一些通用数据写入数据库。
解决此类问题的一种方法是让您的模块根据其包含的数据和格式以及其位置来处理每种日志文件。 使用这种方法,在您的模块中,您将根据那些单独的日志文件呈现给您的界面来处理各种日志文件。 (当我在这里使用接口时,我并不是在谈论语言结构,而是在谈论如何与某些事物进行交互的概念。)
使用这种方法,在我们的模块中,我们可能会有一个switch语句或一系列if-else语句,这些语句根据我们正在处理的日志文件类型将其引导至不同的代码路径。 对于一个日志文件,我们可能会在磁盘上打开一个文件,并读取一行,然后根据一些定界符来拆分该行。 对于另一个,也许我们打开数据库连接并读取一些行。
问题是日志文件定义了我们的高级代码必须使用的接口。 它们实际上在控制我们的代码,因为它们指示我们的代码必须遵循的行为。
我们可以通过指定我们处理的日志文件必须符合的接口来反转此控件,并反转依赖项。 我们甚至不必使用语言级别的界面。
我们可以简单地创建一个称为LogFile的数据类,它是模块的输入。 任何想使用我们模块的人都必须先将其文件转换为我们的格式。
我们还可以创建一个ILogFileSource接口,该类可以实现该接口,以包含解析来自不同来源的日志文件的逻辑。 我们的模块将依赖ILogFileSource并指定解析日志文件所需的方法和数据类型,而不是采用其他方法。
这里的关键点是我们的高级模块应该控制下级模块需要遵守的接口(非语言构造类型),而不是每个下级模块的接口都一时兴起。
考虑这一点的一种方法是较低级别的模块为较高级别的模块提供服务。 较高级别的模块指定该服务的接口,较低级别的模块提供该服务。
在此示例中,我想指出的一件事是,我们知道会有多个日志文件源。 如果我们编写的日志文件解析模块只会对一个源起作用,则可能不值得尝试反转此依赖关系,因为这样做不会带来任何好处。 对于我们来说,使用一个源代码尽可能干净地编写代码,然后在我们拥有其他源代码之后重构它以反转依赖关系并不困难。
仅仅因为您可以反转依赖关系并不意味着您应该这样做。
在这种情况下,因为我们总是在写数据库,所以我认为不需要特别依赖于写出日志文件。 但是,将与数据库交互的所有代码封装到一个地方还是有实际价值的,但这是另一篇文章。
注意我们还没有谈论单元测试
您会看到依赖项倒置和控制倒置的问题与单元测试无关。
只需将一个接口拍到一个类的顶部,然后将其注入到另一个类中可能会有助于单元测试,但不一定会反转控件或依赖项。
我想使用日志分析示例来说明我的观点。 假设我们已经创建了日志解析器以拥有一个switch语句来处理每种类型的日志文件,现在我们要对代码进行单元测试。
没有理由为什么我们不能创造IDatabaseLogFile,ICSVFileSystemLogFile,IEventLogLogFile和IAnNotReallyDoingIoCLogFile,他们都进入了我们作为依赖LogFileParser的构造函数,然后编写单元测试通过在每个嘲笑。
可以肯定地说,这是一个极端的例子,但要点是将接口推到类上并不是IoC制造的。
我们不应该尝试实现这一原理以简化编写单元测试的过程。 难以编写单元测试应该给我们一些提示:
- 我们班上想做的太多
- 我们的课有很多不同的依赖
- 我们的课需要很多准备工作
- 我们的课程与其他课程一样,仅对不同的输入执行相同的操作
所有这些提示都告诉我们,我们可能想反转控件和反转依赖项以改善类的总体设计,而不是因为它使测试更容易。 (尽管它也应该使测试更容易。)
好吧,好吧,依赖倒置与控件倒置是一样的吗?
简短的回答:是的。
这取决于您对控制的含义。 可以颠倒三个基本的“控件”。
- 接口的控制。 (这两个系统,模块或类如何相互交互并交换数据?)
- 流的控制。 (什么控制程序的流程?当从程序驱动到事件驱动时,就会发生这种控制反转。)
- 对依赖项创建和绑定的控制。 (这是控制IoC容器所做的这种反转。这种反转会将对依赖关系的实际创建和选择的控制权传递给第三方,而该第三方对所涉及的其他2个方都是中立的。)
这3种中的每一种都是依赖性反转的一种特定形式,甚至可能涉及多种依赖性反转。
因此,当有人说“控制权倒置”时,您应该思考“这里的控制权倒置了什么?”。
依赖倒置是我们在设计软件时使用的原则。
控制反转是一种适用于此的特定模式。
大多数人只将控制反转视为上面的#3,将依赖性创建和绑定的控制反转。 这是IoC容器和依赖项注入扎根的地方。
我们可以从中学到什么?
我的目标是,不再将依赖于注入的控制反转和依赖反转的概念归为一类。
我们已经了解到,依赖倒置是指导从中得出的许多其他实践的核心原则。
每当我们应用某种模式时,我们都应该寻找它所依赖的核心原则,以及它正在帮助我们解决什么问题。
通过对依赖反转和控制反转的基本了解,我们具备了了解依赖注入并更好地了解它试图解决什么特定问题的先决知识。 (我将在另一篇文章中介绍。)
参考:从 基础到基础:什么是依赖关系反转? 是IoC吗? 第1部分和基础知识基础:什么是依赖关系反转? 是IoC吗? 来自JCG合作伙伴 John Sonmez的第2部分 ,在“ 使复杂结构简化”中 。
相关文章 :
翻译自: https://www.javacodegeeks.com/2011/08/what-is-dependency-inversion-is-it-ioc.html