David 是这本书的技术审阅,关于控制反转的这两篇精彩文章交叉发布于他的博客,您可以在那里找到更多精彩内容。
当我最初学习编程时,我编写的代码都遵循一个特定的模式:我编写指令给计算机,它会逐个执行。如果我想使用在其他地方编写的实用程序,例如在第三方库中,我会直接从我的代码中调用这些实用程序。像这样的代码可以被描述为采用了“传统的控制流”。也许这只是我的偏见,但这在我看来仍然是显而易见的编程方式。
尽管如此,我今天编写的大部分代码都在更广泛的上下文中运行;在这个上下文中,控制正在被反转。这是因为我通常在使用某种框架,即使我的代码没有直接依赖于它,框架也会将控制权传递给我的代码。框架允许我插入自定义行为,而不是我的代码调用更通用的代码。像这样设计的系统正在使用所谓的控制反转(简称 IoC)。
这种情况可以这样描述:通用框架提供自定义代码可以插入其行为的点。
即使我们中的许多人都熟悉在这样的框架中编写代码,我们往往不愿意在我们自己设计的软件中应用相同的想法。实际上,这似乎是一件奇怪甚至不可能的事情。这肯定不是“显而易见”的编程方式。
但是 IoC 不必局限于框架——相反,它是程序员工具箱中特别有用的工具。对于更复杂的系统,它是避免我们的代码变得混乱的最佳方法之一。让我告诉你为什么。
力求模块化
软件很容易变得复杂。每个程序员都经历过纠结、难以处理的代码。这是一个这样的系统的图表
也许不是一个很有帮助的图表,但有些系统在工作中会给人这种感觉:一大堆令人望而生畏的代码,感觉不可能理解。
解决这种复杂性的常用方法是将系统分解成更小、更易于管理的部分。通过将其分成更简单的子系统,目的是降低复杂性,并使我们能够更清晰地思考每个子系统。
我们将系统的这种质量称为模块化,我们可以将这些子系统称为模块。
关注点分离
我们大多数人都认识到模块化的价值,并努力将我们的代码组织成更小的部分。我们必须决定哪些内容放入哪个部分,而我们这样做的方式是通过关注点分离。
这种分离可以采取不同的形式。我们可以按功能区域(身份验证系统、购物车、博客)或按详细程度(用户界面、业务逻辑、数据库)或两者兼而有之来组织事物。
当我们这样做时,我们的目标往往是模块化。但出于某种原因,系统仍然很复杂。在实践中,处理一个模块需要询问系统另一部分的问题,该部分又调用另一个部分,然后又回调到原始部分。很快我们的头就疼了,我们需要躺下休息一下。哪里出错了?
关注点分离是不够的
可悲的事实是,如果代码唯一的组织因素是关注点分离,那么系统最终不会是模块化的。相反,各个部分会纠缠在一起。
很快,我们组织每个模块内容的努力就被模块之间的关系破坏了。
如果您不考虑关系,这自然会发生在软件上。这是因为在现实世界中,事物就是一个混乱的、相互关联的网络。当我们构建功能时,我们意识到一个模块需要了解另一个模块。稍后,另一个模块需要了解第一个模块。很快,一切都了解一切。
像这样的软件的问题在于,由于关系网络,它不是较小子系统的集合。相反,它是一个单一的大型系统——而大型系统往往比小型系统更复杂。
通过解耦提高模块化
这里的关键问题是,模块虽然看起来是独立的,但它们彼此之间的依赖关系紧密耦合。让我们以两个模块为例
在此图中,我们看到 A
依赖于 B
,但 B
也依赖于 A
。这是一个循环依赖。因此,这两个模块实际上与单个模块一样复杂。我们如何改进情况?
通过反转控制消除循环
有很多方法可以解决循环依赖。您可能可以将共享依赖项提取到一个单独的模块中,供其他两个模块依赖。您也可以创建一个额外的模块来协调这两个模块,而不是让它们相互调用。或者您可以使用控制反转。
目前,每个模块都相互调用。我们可以选择其中一个调用(假设是 A
对 B
的调用),并反转控制,以便 A
不再需要了解关于 B
的任何信息。相反,它公开了一种插入其行为的方式,B
可以利用它。可以用下图表示
现在 A
对 B
没有具体的了解,我们孤立地考虑 A
。我们刚刚减少了我们的心智负担,并使系统更模块化。
这种策略对于更大的模块组仍然有用。例如,三个模块可能相互依赖,形成一个循环
在这种情况下,我们可以反转其中一个依赖项,从而获得单一的流向
同样,控制反转又一次拯救了我们。
控制反转的实践
在实践中,反转控制有时会让人感觉不可能。当然,如果一个模块需要调用另一个模块,难道仅仅通过重构就无法逆转这种情况吗?但我有好消息。您应该始终能够通过某种形式的反转来避免循环依赖(如果您认为您找到了一个无法避免的例子,请告诉我)。这并不总是最明显的编写代码的方式,但它可以使您的代码库更容易处理。
有很多不同的技术可以做到如何实现这一点。经常被谈论的一种技术是依赖注入。我将在本系列文章的第二部分中介绍其中的一些技术。
关于如何在更广泛的代码库中应用这种方法还有更多要说的:如果系统由不止几个文件组成,我们从哪里开始?同样,我将在本系列的后续文章中介绍这一点。
结论:复杂优于繁琐
如果您想避免您的代码变得混乱,仅仅分离关注点是不够的。您必须控制这些关注点之间的关系。为了获得更模块化系统的好处,您有时需要使用控制反转来使控制流向与自然而然的方向相反。
Simple is better than complex.
但同时也指出
Complex is better than complicated.
我认为控制反转是选择复杂而不是繁琐的一个例子。如果我们不在需要时使用它,我们创建简单系统的努力将会纠缠成繁琐。反转依赖关系允许我们以少量的复杂性为代价,使我们的系统不那么繁琐。
更多信息
- 本系列文章的第二部分:Python 中反转控制的三种技术。