昨天經過一朋友的SPACE,看到有關於控制反轉的討論,一時技癢,寫下一段留言,完後由於比較長的時間沒接觸這幾個單詞,因此又去查了些資料,重新整理了一下,跟大家一起討論。
整理之前,首先要說說“依賴”,什麼是依賴,依賴就是關聯,UML中定義的“關聯”是最泛泛的一種關係,表現為兩個類圖之間有根線就有關聯,我個人理解成,在C/C++中,A include了另一個標頭檔B,JAVA/.Net中A using了另一個package或者unit B,則兩者就有了關聯,A依賴於B,因為假設沒有依賴關係,A為啥要include B?肯定了發生了某種調用(B.Call())或者引用(如B做為某個類變數或者參數變數)。所以偶把這個理解成依賴。
網上討論的假設B發生變化那A也發生變化成為依賴,偶覺得可能會有一定的誤導,因為假設變化的是B內部,介面不變,那A為什麼要變?或者B增加一個介面A不調用,A也不用變,但是A依然依賴於B。
這是關於依賴,接下來是關於標題三個名詞,在這裡不想一個個去解釋,因為那可能有種就事論事的感覺,想跟大家討論一些比較本質的東西。
在面對一個複雜事務的時候,我們的處理辦法是什嗎?毫無疑問是分解,想想那些日理萬機的領導,凡事不論巨細都要過問的話那是不可能,因為個人的精力有限,而複雜度是隨著規模的增大而呈數量級的變化的,所以領導怎麼處理?分解,分成市場總監,財務總監,技術總監,行政總監等一個個角色,每個角色負責一塊,到時候跟領導彙報各自的問題就OK了,領導來做決策。
以軟體來類比的話,這裡的總監就是一個個模組,彙報就是OO中的介面(Interface),領導就是架構,把全部的模組串起來完成一個既定的大目標(如實現某個方案)。那工程師或者財務人員呢?毫無疑問就是一個個具體的CLASS了,是實現具體某個功能的執行體,如一項編程工作或者整理出某個報表。
由此可見,每天在我們周圍發生的這些事情,這些公司內部的一些行為都可以映射成整個軟體構架。那麼從公司的這種組織行為我們可以得到什麼樣的思考?首先有了分解我們可以降低一些的複雜度,但接下來呢,軟體最複雜的在於什嗎?在於變化。公司也一樣,外界要面臨重重生存壓力,內部可能會有一些公司層面的或者個人層面的問題,甚至人員跳槽也會帶來一定的風險,那公司怎麼處理? 隔離。把容易變化的跟不容易變化的相對穩定的隔離開來,這樣就能做到控制影響到最小。那麼隔離通過什麼來實現?抽象。為什嗎?因為抽象是相對穩定的。
這裡舉個比較好笑的例子:一個即將做領導的兒子問曾經做領導的父親怎麼才能平步青雲,父親說你不能說假話,因為老百姓會不答應,也不能說實話,因為領導會對你有意見,兒子思索良久問:那我該說什麼? 父親意味深長的說:空話
笑話不但好笑,還能反映一定的道理,為啥空話這麼有用,因為他是抽象的,而抽象不涉及到一些具體的資料或者事務,因此他是穩定的,是不容易變化的,同時基本上也是正確的。所以我們經常在公開場合聽到類似的話“我們要團結同志,努力進取,提高工作效率,降低工作成本。。。”你能說這是錯的嗎,當然不能,所以既然是非常穩定的對的,所以這種依賴就很可靠啦。
公司也一樣,假設領導依賴於工程師的能力,成天問這個問那個,那如果有一天工程師跳槽了怎麼辦?領導處理的都是一些非常重要宏觀的事務,不可能因為某個小小的工程師而產生什麼大的影響,因此他不會直接依賴工程師,而是依賴於一個叫技術總監這樣的一個抽象。如果工程師離職,那換個工程師繼續替代他以前做的事情就行了,而且都實現的是技術總監規定好的介面,這樣對領導就沒什麼影響了,也只有這樣變化就被隔離開來了。
再反過來想想軟體,其實上面描述的都是一些對軟體而言非常有意義的做法,OO中非常重要的一點就是模組之間依賴於抽象的介面,而不是具體的實現,為啥,因為抽象是相對穩定的。一個IO他必然就有READ/WRITE這兩個抽象,至於具體是磁碟還是鍵盤,那是下面的實現不同了,通過這種構架,能保持軟體的彈性與可維護性。
由公司的行為還有一點容易受到啟發的就是公司的組織構架,公司的層次可以映射成軟體的分層,領導是架構層,下面通過一個個介面去管控一個個CLASS。我們設計軟體的時候毫無疑問也應該這樣。設計好架構設計介面,設計好介面再去調用一個個的API或者CLASS去實現某一個具體的實現比如資料庫的讀寫或者SOCKET資料的收發。每個地方有自己相對對立的職責,各盡其職。如果上來就是一個個API,那相當於一大群工程師既做商務談判,又去編碼,那就雜亂無章啦。這樣的結果感覺都是一樣:混亂。
最後言歸正傳,談談上面的三個名詞,依賴倒置DIP(Dependency Inversion Principle)在馬丁大叔的傑作《敏捷開發:原則,實踐與模式》中描述的比較清楚,高層模組不應該依賴於底層模組,而應該兩者都依賴於一個抽象,底層模組實現抽象。相信這點已經在上面討論的比較清楚了,就好比儘管領導歸根結底要依賴工程師來做事,但他不會直接依賴你,而是依賴於一個總監的抽象。這就是倒置,哪裡倒了?這種依賴關係倒了,引入了一個新的中介層,一個抽象。以前傳統的過程設計中是從上到下的一條依賴線,現在是平的一條領導到總監,然後是一條從下往上的工程師到總監的關聯線。
控制反轉(Inversion of Control)其實有點類似,主要是OO中提出了架構的概念,什麼是架構,按王氏兄弟的《道法自然》中的描述,主要是一個動態環境體,可以處理一些比較複雜的事務或者邏輯,跟類庫靜態行為不大一樣,類庫都是一個個等待別人調用的服務。 那麼控制體現在什麼地方?傳統的做法,我們在自己的程式中調用一個個類庫去完成一個個功能,類庫是我們的執行體,但是邏輯演算法等是自己實現的,這就是自己的控制,但架構不一樣,邏輯演算法都是它來實現,我們只要提供它幾個需要的介面或者執行體就可。這就是反轉,控制在架構這邊了,主要是簡化了一定的工作量,對於一些常見的業務情境不需要自己去重複實現罷了。那麼控制反轉主要應用的是模板方法等模式,個人覺得觀察者模式是一個比較典型的控制發轉的執行個體,因為論詢observer是subject這邊實現的一個邏輯,而observer 只要實現notify這樣一個介面即可。所以其實也沒什麼。
依賴注入(Dependency Injection)其實相比以上兩者應該不是一個層次的概念,主要是表現為利用建構函式或者介面來完成具體的工作類的綁定而已,這麼想好了,比如要完成一個開發工作,可以讓總監自己去指定工程師甲來完成這個工作,但為了更大的彈性,可以讓總監提供這麼個介面
AssignJob(Engineer engineer) {engineer.do();}
這樣總監可以預設讓甲去完成這個工作,但如果某種原因例如甲請假,那麼就可以通過這個介面去讓乙去做這件事情,這樣在發生變化的時候就有彈性了。順便提一下,軟體多數通過CONFIG來達到更大的靈活性,由於.NET/JAVA等引入了反射的概念,可以在RUNTIME期間動態去建立類,這個功能就很強大了,參考如上的業務需求,我們就可以把類名放在CONFIG中,通過反射去載入不同的類從而完成不同的實現。這也是很多架構(如structs)的最基本的做法
以上是一些個人看法,感覺一些OO的名詞聽起來玄乎,其實也就那麼回事,只要掌握好OO的幾個基本原則,其實基本上都可以推匯出來。呵呵。個人愚見,希望跟大家交流
附上王氏兄弟的更精彩精闢的講解:http://www.contextfree.net/wangyw/source/dip_ioc.html