- Live軟件開發面面談
- 潘俊編著
- 1348字
- 2019-07-30 17:55:02
2.1 控制反轉
所謂控制反轉,是針對程序正常的控制流程而言的。一般情況下,正在運行的函數或對象的方法調用另一個函數或對象的方法,控制也就從調用方轉移到被調用方,直到被調用方運行完畢,才返回給調用方。但是某些情況下,需要被調用方中途將控制傳遞回調用方,這種控制轉移的方向與正常方向相反的現象就稱為控制反轉。最常見的有以下幾種情況。
(1)被調用方需要一直運行,無法返回,而在不確定的時間又要運行調用方的邏輯。圖形用戶界面程序的開發就是很好的例子。程序員使用圖形用戶界面的通用類庫里的控件創建視圖,視圖一直運行,收聽用戶操作觸發的事件。用戶什么時候輸入文本框、單擊按鈕是不確定的。當這些事件發生時,視圖則要通過事件的處理程序,執行項目特定的業務邏輯。
(2)被調用方運行時間較長,調用方不愿或者不能等待被調用方執行完成。在正常的控制流程下,在被調用方執行完畢返回前,調用方一直等待,即處于所謂阻塞狀態。假如采用控制反轉的模式,將調用方等待被調用方返回后要運行的邏輯以某種方式傳遞給被調用方,然后新開一個線程,讓被調用方在其中運行,調用方就可以保有控制,去做其他事情。函數的異步調用就是這種情況。
(3)被調用方是提取多個特定程序中重用的公共的邏輯,被調用時還需要補充原來程序中特定的邏輯。例如,JavaScript中Array的forEach()、map()等方法,將對一個列表數據結構的遍歷邏輯提取出來,被調用時需要傳入一個函數,以實現循環中特定的邏輯。
控制反轉發生的共同前提是:調用方是項目特定的代碼,被調用方是具有某種功能的通用程序,在開發中無法也不應該被修改。否則若被調用方也是一般的項目(ad hoc)代碼,當它需要訪問調用方的功能時,就可以直接在代碼中加入,控制的轉移也就是正常的。
比如對于以上第一種情況,假如圖形用戶界面的類庫不是通用的,而是程序員每開發一個從頭寫出的項目,每個控件都是獨一無二的,那就可以直接在一個按鈕的實現代碼內部添加它要處理的事件的響應程序。應用程序運行時,控件執行事件處理程序時也僅僅是調用自己的一個方法。這么極端的情況當然不會發生,一種緩和的變體卻是可能的,并且實實在在地存在。在這種情況下,控件仍然來自現成的類庫,向視圖上添加的卻不是它們的實例,而是實例化自它們的繼承類,在繼承類中添加了事件處理程序。這樣控件執行事件處理程序時,也沒有將控制返回給它的調用方。理論上,開發圖形用戶界面程序時,確實可以采用這種方式,實際上Android的用戶界面框架還特意提供了這種途徑,作為控件基類的View有若干公開的方法,例如onTouchEvent(),當一個按鈕被單擊時,這個方法就會被系統調用。所以要為按鈕添加響應該事件的邏輯,可以在按鈕的繼承類中實現這個方法。然而現實中沒有多少程序員會采用這種方法,因為采用事件發布者和訂閱者的模式,只需使用現成的控件,添加事件處理程序和調用一個方法一樣簡單,而為每個控件實例都創建一個繼承類就煩瑣得多。由這些討論也可以從反面看出,事件實現的控制反轉對圖形用戶界面程序開發來說,是一種多么有效和重要的模式。
對于第二種情況,假如被調用方是普通的項目代碼,調用方不愿等待它運行完畢后返回,仍然要創建新的線程,但是不必將被調用方返回后要運行的邏輯再傳遞給它,因為此時被調用方和調用方一樣,也在程序員的控制之下,直接將這些邏輯寫在被調用方中就可以了。