Martin Fowler:Inversion Of Control(控制反转)

111 阅读6分钟

有道无术,术尚可求,有术无道,止于术。

原文地址

作者:Martin Fowler

发布时间:2005 年 6 月 26 日

控制反转Inversion of Control)是在扩展框架时常见的一种现象,实际上它通常被视为框架的一个定义性特征。

让我们通过一个简单的例子来说明,假设我正在编写一个程序,通过命令行查询用户的一些信息,代码可能像这样:

puts 'What is your name?'
name = gets
process_name(name)
puts 'What is your quest?'
quest = gets
process_quest(quest)

在这个交互中,我的代码是掌控一切的,它决定何时提问,何时读取回答,何时处理这些结果。

如果我使用窗口系统来实现类似的功能,我将通过配置一个窗口来实现,代码可能会是这样:

require 'tk'
root = TkRoot.new()
name_label = TkLabel.new() {text "What is Your Name?"}
name_label.pack
name = TkEntry.new(root).pack
name.bind("FocusOut") {process_name(name)}
quest_label = TkLabel.new() {text "What is Your Quest?"}
quest_label.pack
quest = TkEntry.new(root).pack
quest.bind("FocusOut") {process_quest(quest)}
Tk.mainloop()

现在,这些程序之间的控制流程有了很大的区别,特别是在 process_nameprocess_quest 方法何时被调用的控制上。在命令行形式下,我控制着这些方法的调用时机,而在窗口示例中,我却不能控制这一点。相反,我将控制权交给了窗口系统(通过 Tk.mainloop 命令)。然后,它根据我在创建窗体时设置的绑定来决定何时调用我的方法。

这时控制被反转了,它调用我,而不是我调用框架。这种现象被称为控制反转(Inversion of Control),也被称为好莱坞原则Hollywood Principle),别找我们,我们会找你。

拉尔夫·约翰逊(Ralph Johnson)和布莱恩·富特(Brian Foote):框架的一个重要特征是用户为定制框架而定义的方法通常是由框架内部调用的,而不是由用户的应用代码调用。框架通常扮演主程序的角色,协调和排序应用活动。这种控制反转赋予框架作为可扩展骨架的能力,用户提供的方法会根据特定的应用需求调整框架中定义的通用算法。

控制反转是框架之间差异的关键部分,库本质上是一组可以调用的函数,通常现在会以类的形式组织起来,每个调用执行某些工作后会将控制权返回给客户端。

而框架则体现了某种抽象设计,内置了更多的行为。为了使用框架,你需要将自己的行为插入到框架的各个位置,无论是通过子类化还是通过插入你自己的类。然后,框架的代码会在这些点调用你的代码。

有多种方式可以将你的代码插入并被调用。在上面的 Ruby 示例中,我们在文本输入框上调用了一个 bind 方法,传递了一个事件名和一个 Lambda 函数作为参数。每当文本输入框检测到该事件时,它会调用闭包中的代码。使用闭包非常方便,但许多语言并不支持它。

另一种方式是让框架定义事件,并让客户端代码订阅这些事件。 .NET 就是一个很好的例子,它提供了语言特性,允许人们在控件上声明事件,然后,你可以通过使用委托将方法绑定到这些事件上。

上述方法(它们其实是相同的)在单个用例中运行良好,但有时你可能想将多个必需的方法调用结合在一个扩展单元中。在这种情况下,框架可以定义一个接口,客户端代码必须实现这个接口来处理相关的调用。

EJB 是这种控制反转风格的一个很好的例子。当你开发一个会话 Bean 时,你可以实现各种方法,这些方法会在 EJB 容器的不同生命周期点被调用。例如,会话 Bean 接口定义了 ejbRemoveejbPassivate(存储到二级存储)和 ejbActivate(从被动状态恢复)。你无法控制这些方法何时被调用,只能控制它们的行为,容器调用我们,我们并不调用它。

这些是复杂的控制反转案例,但你会在更简单的情况中遇到这种效果。模板方法就是一个很好的例子:超类定义了控制流,子类通过覆盖方法或实现抽象方法来扩展它。因此,在 JUnit 中,框架代码会为你调用 setUptearDown 方法,以创建和清理测试固件。它负责调用你的代码响应,所以,控制再次被反转。

由于 IoC 容器的兴起,近年来关于控制反转的含义出现了一些混淆;一些人将这里的一般原则与这些容器所使用的特定控制反转风格(如依赖注入)混淆了。这个名称有点令人困惑(也有些讽刺),因为 IoC 容器通常被视为 EJB 的竞争对手,但 EJB 同样也使用了控制反转,甚至使用得更多。

词语来源:

据我所知,控制反转这一术语首次出现在 JohnsonFoote1988 年发表的《Designing Reusable Classes》论文中,该论文由《面向对象编程杂志》(Journal of Object-Oriented Programming)发布。这篇论文至今仍然具有很高的价值,即使已经过去了十五年,现在读来依然值得一看。作者认为这个术语的来源可能是从别的地方借用的,但他们记不清了。之后,这个术语逐渐被面向对象社区接受,并且出现在了《设计模式:可复用面向对象软件的基础》一书中。

另一个更为生动的同义词好莱坞原则(Hollywood Principle)似乎起源于 Richard Sweet1983 年在关于 Mesa 的论文中。在一系列设计目标中,他写道:“不要联系我们,我们会联系我们你(好莱坞法则),一个工具应该安排 Tajo 在用户希望向工具传达某个事件时通知它,而不是采用请求用户输入命令并执行的模式。John Vlissides曾在 C++ 报告中撰写了一篇专栏,详细解释了这一概念,并使用了好莱坞原则这一名称。

感谢 Brian FooteRalph Johnson 为我提供词语来源帮助。