今天要講的是以下三個概念: - 控制反轉 :IoC, Inversion of Control - 依賴註入:DI, Dependency Injection - 依賴查找:DL, Dependency Lookup, Service Locator 什麼是控制反轉 先說正常的控制流程是怎樣的: 比 ...
今天要講的是以下三個概念: - 控制反轉 :IoC, Inversion of Control - 依賴註入:DI, Dependency Injection - 依賴查找:DL, Dependency Lookup, Service Locator
什麼是控制反轉
先說正常的控制流程是怎樣的: 比如我寫了一個方法`process()`,在程式中的某處,我會自己調用這個方法,完成一些操作。如下示例:獲取了`input`參數,然後傳給`process()`進行處理。public static void main(String args[]) { Scanner sc = new Scanner(System. in); String input = sc.nextLine(); process(input); // do something with the input }
然後說反轉的控制流程是怎樣的: 這個`process`方法,我自己不會調用,而是把這件事交給某個模塊(Container/Framework)去調用,如下示例:用戶在TextField中輸入一些`input`,然後點擊Button來process。這個`process()`是綁定在Button的ActionListener上的,也就是說它運行的控制權交給了Swing框架,只有當Button被按下的時候,才會調用`process()`。
public void prepareAndShowGUI() { JFrame mainFrame = new JFrame(); JPanel controlPanel = new JPanel(); JButton processButton = new JButton("Process"); final JTextField inputText = new JTextField(10); processButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String input = inputText.getText(); process(input); // do something with the input } }); controlPanel.add(processButton); controlPanel.add(inputText); controlPanel.add(statusLabel); mainFrame.setSize(400, 100); mainFrame.add(controlPanel); mainFrame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent windowEvent){ System.exit(0); } }); mainFrame.setVisible(true); }
有一個很有名的好萊塢原則和控制反轉的概念類似:Hollywood Principle - "Don't call us, we'll call you"。
包,框架和容器
先解釋包(library)和框架(framework),下兩段話摘錄自Martin Fowler的文章Inversion Of Control:A library is essentially a set of functions that you can call, these days usually organized into classes. Each call does some work and returns control to the client. A framework embodies some abstract design, with more behavior built in. In order to use it you need to insert your behavior into various places in the framework either by subclassing or by plugging in your own classes. The framework's code then calls your code at these points.為了提高效率,代碼復用,我們寫的程式里會調用別人寫的包,這時我們的code就對別人的code有了依賴(dependency)。 為了更加提高效率,更加代碼復用,我們只寫和behavior有關的代碼,其它的事情都交給framework來做。 再解釋容器container的概念,在這裡可以認為是framework運行的環境,或者提供了對framework的實現。(註:這裡說的container和docker是兩個概念) 回到主題,我認為,控制反轉的出現,是對framework和container的一種理論支撐。我們已經習慣了包的調用,因為它很容易理解,其實就是調用方法,獲得返回值,這裡的控制流程是正向的,思維也是正向的。但是對框架的應用,剛上手往往很難理解,因為整個程式的控制流程由框架在掌握,思維需要轉個彎。
房子,傢具和裝修
下麵我用一個比喻,來闡述包和框架的概念: 原始時期,寫程式就像搭小木屋,自己一根根木頭把房子造好,然後又一根根木頭打造桌子椅子,什麼事都親力親為,所有的功能都自己寫code實現。這個時候,我什麼外部的code都沒用。 慢慢地,我們發現可以用一些別人現成做好的東西,比如張三家產的石制桌椅,質量精良,比我自己做的木頭桌椅好多了,那我就拿過來用啊,自己只需要搭個屋子就好了,省了很多心力。這裡就用到了library。 再後來,乾這行的人多了,行業發展,自然產生了分工,有的人專門造房子,有的人專門做精裝修,有的人專門做傢具。我要造一間房子,傢具可以用別人生產的,房子可以用別人建好的毛坯房,自己只需要做做精裝修就好了,又省了不少力氣。 - 造房子相當於做framework/container - 做傢具相當於做library - 精裝修相當於寫業務邏輯 這裡的房子和傢具有一點特殊,做好了一個版本後就可以無限複製,但是精裝修的活每個客戶的需求都不一樣,需要大量勞動力,所以在這個行業里,一般大多數人都是做精裝修的,用了別人的房子,用了別人的傢具。 傢具因為裝修的時候處處能碰到,看的見摸得著,所以比較熟,但是房子這個東西,比較抽象,往往只知道卧室該放些什麼東西,廚房該放些什麼東西,但是卧室和廚房是怎麼連接起來的,包括房子中的各個部分是如何連接起來的,就不容易明白。
控制反轉的實現
首先要問一個問題,為什麼要做控制反轉?我用包不行嗎?這個問題我思考了很久,最終我的想法是:實現業務邏輯,用包就夠了,即使用了某些框架,其實也不需要你去具體實現控制反轉。你只需要知道,這個框架做了什麼,我的業務邏輯在這個框架中寫在什麼位置就可以了,你不需要真的瞭解框架是怎樣實現的。可以把框架想象成一個黑盒,你只需要知道自己的應用放在黑盒的哪個位置就好了。 但是,如果要開發一個框架,就需要用到控制反轉,理解控制反轉,實現控制反轉。Martin講到了以下幾種實現方法,但是他同時也說,其實這些方法大同小異,原理都是一樣的(就是控制反轉啊)。 Single calls - closure: whenever the event is triggered, the program calls the closure binded to the event - have the framework define events and have the client code subscribe to these events Multiple calls - framework: define an interface that a client code must implement for the relevant calls - template method: the super-class defines the flow of control, subclasses extend this overriding methods or implementing abstract methods to do the extension 看了之後一臉懵逼,那就舉個例子吧。 例: Closure 把閉包和控制反轉放到一起比較,兩者有類似的地方。之前說過,傳統的控制邏輯是,我調用某個方法/包來實現某個功能(比如log),或者處理數據得到一些返回值。因為Java函數設計的時候原則就是給定一些輸入值,得到一些輸出值,或者沒有返回值。與之不同的反轉控制邏輯是,我告訴你要乾一些什麼事情(輸入),我該幹嘛幹嘛,然後你覺得時機合適的時候再來叫我。這個不就是閉包把函數當成一個參數傳來傳去嗎?// calling service method services.countGithubLikes(function(data) { // how to process the data after callback ... }); // service method countGithubLikes: function(callback) { // counting ... callback(); // calling callback to process data }