控制反轉

2005 年 6 月 26 日

控制反轉是擴充架構時常見的現象。事實上,它通常被視為架構的定義特徵。

讓我們考慮一個簡單的範例。假設我正在撰寫一個程式,以便從使用者取得一些資訊,而我正在使用命令列查詢。我可能會這樣做

  #ruby
  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 命令)。然後,它會根據我在建立表單時建立的繫結,決定何時呼叫我的方法。控制權被反轉了,它呼叫我,而不是我呼叫架構。這種現象就是控制反轉(也稱為好萊塢原則,即「不要呼叫我們,我們會呼叫你」)。

架構的一個重要特徵是,使用者定義用於調整架構的方法通常會在架構本身中呼叫,而不是從使用者的應用程式程式碼呼叫。架構通常扮演主程式角色,用於協調和排序應用程式活動。這種控制反轉賦予架構作為可擴充骨架的能力。使用者提供的這些方法會調整架構中定義的通用演算法,以適用於特定應用程式。

-- Ralph Johnson 和 Brian Foote

控制反轉是框架與函式庫不同的關鍵部分。函式庫基本上是一組你可以呼叫的函式,現今通常組織成類別。每個呼叫都會執行一些工作,然後將控制權傳回給用戶端。

框架體現了一些抽象設計,內建更多行為。要使用它,你需要透過子類別化或插入你自己的類別,將你的行為插入框架中的不同位置。然後框架的程式碼會在這些點呼叫你的程式碼。

你可以透過各種方式插入你的程式碼以供呼叫。在上面的 Ruby 範例中,我們在文字輸入欄位上呼叫 bind 方法,傳遞事件名稱和 Lambda 作為引數。每當文字輸入方塊偵測到事件時,它就會呼叫封閉中的程式碼。像這樣使用封閉非常方便,但許多語言並不支援它們。

另一種方法是讓框架定義事件,並讓用戶端程式碼訂閱這些事件。.NET 是個很好的平台範例,它具有語言功能,允許人們在小工具上宣告事件。然後你可以使用委派將方法繫結到事件。

上述方法(它們實際上是相同的)適用於單一案例,但有時你會想要在單一擴充單元中結合幾個必要的呼叫方法。在這種情況下,框架可以定義一個介面,用戶端程式碼必須針對相關呼叫實作該介面。

EJB 是這種控制反轉風格的良好範例。當你開發會話 Bean 時,你可以實作各種方法,這些方法會在不同的生命週期點由 EJB 容器呼叫。例如,會話 Bean 介面定義了 ejbRemoveejbPassivate(儲存在次要儲存體中)和 ejbActivate(從被動狀態還原)。你無法控制這些方法的呼叫時機,只能控制它們的作用。容器呼叫我們,我們不呼叫它。

這些是控制反轉的複雜案例,但你會在更簡單的情況下遇到這種效應。一個 範本方法 是個很好的範例:超類別定義了控制流程,子類別擴充這些覆寫方法或實作抽象方法來執行擴充。因此在 JUnit 中,框架程式碼會為你呼叫 setUptearDown 方法,以建立和清除你的測試裝置。它執行呼叫,你的程式碼做出反應 - 因此控制權再次被反轉。

由於 IoC 容器的興起,現今對於控制反轉的意義有些混淆;有些人將這裡的一般原則與這些容器使用的特定控制反轉風格(例如 相依性注入)混淆。這個名稱有點令人困惑(而且具有諷刺意味),因為 IoC 容器通常被視為 EJB 的競爭對手,但 EJB 也大量使用控制反轉(甚至更多)。

詞源:據我所知,控制反轉一詞最早出現在 Johnson 和 Foote 的論文 設計可重複使用的類別 中,該論文於 1988 年由物件導向程式設計期刊出版。這篇論文是其中一篇保存良好的論文 - 即使在 15 年後仍非常值得一讀。他們認為他們從其他地方獲得了這個術語,但記不起是什麼。然後,這個術語滲透到物件導向社群,並出現在 四人幫 一書中。較為生動的同義詞「好萊塢原則」似乎源自 理查·史威特在 1983 年發表的關於 Mesa 的論文。在設計目標清單中,他寫道:「不要呼叫我們,我們會呼叫你(好萊塢法則):工具應安排 Tajo 在使用者希望將某些事件通知工具時通知工具,而不是採用『詢問使用者指令並執行』的模式。」約翰·維利塞德斯為 C++ 報告撰寫了一篇 專欄,在「好萊塢原則」標題下對這個概念提供了良好的解釋。(感謝布萊恩·富特和拉爾夫·約翰遜在詞源方面提供協助。)