前言
隨著前端承擔的職責越來越重,前端應用向著複雜化、規模化的方向發展。大型專案模組化是一種趨勢,不可避免模組之間要相互依賴,此外還有很多第三方包。這樣的話如何去管理這些繁雜的檔案,是一個不可避免的話題。此時作為一種已經被實踐證明過的思想模式一直得到大家的青睞,這就是控制反轉(IOC)。
IOC定義
先看一下維基百科上的定義:
控制反轉(Inversion of Control,縮寫為IoC),是物件導向程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。
原則
- 高層模組不應該依賴低層模組。兩個都應該依賴抽象
- 抽象不應該依賴具體實現
- 面向介面程式設計,而非面向實現程式設計
針對前端來說,介面的概念不那麼清晰明瞭,不像強型別語言。 概念是比較枯燥的,下面結合例子來看一下可能更好理解一點。
目的
根據概念可以看到最主要的目的就是降低耦合,提高擴充套件性。在深究之前,我們先看下程式碼耦合
程式碼耦合
所謂耦合,可以如下圖顯示:
比較清晰明瞭,程式碼相互之間的聯絡太直接:
假如obj2報錯,那麼整個系統也都報錯了。
所以我們的目的就是降低二者之間的耦合度,
結合圖來說比較清晰,
如果兩者不這麼直接的發生關係,那麼相互影響的概率就小了那麼多了。
另外,這是比較少的模組,常規專案裡顯然不僅僅是隻有這麼少,想象一下多個模組的場景: 這裡除了耦合之外,不同齒輪之間的依賴關係也是個頭疼的問題,迭代個幾個版本之後發現,這是什麼東西,一動就有bug。。。。
所以IOC就是來解決上述問題的。 其常見方式是依賴注入和依賴查詢。在js領域裡面最出名的就是angular中大量使用了依賴注入。文字比較蒼白,我們可以通過例子來看看。
例項
就從nba來說,有那麼一些球星,我們想知道他所屬的球隊,那麼可能就像下面這個情況:
//球隊資訊
class RTeam {
constructor(){
this.name = '火箭'
}
}
// 球員資訊
class Player{
constructor(){
this.team = new Team()
}
info(){
console.log(this.team.name)
}
}
// 球員ym
let ym = new Player()
ym.info() // ‘火箭’
複製程式碼
看起來挺好的,球員player依賴於某個球隊RTeam 當呼叫的時候主動去載入球隊即可。此時的控制權在player這裡。
假如這個時候,球員發生交易了,球隊資訊更換了,轉換到team2了。
這時候我們就需要去修改player裡的程式碼了,因為球員那裡直接寫死了對RTeam的依賴,這種可擴充套件性是很差的。
這不是我們所想要的,需要重新思考下依賴關係處理了。
球員和球隊之間非得這麼直接粗暴的發生聯絡嗎,
一個球員對應一個球隊的話,未來會發生變化的可能性太大了,畢竟不止一個球隊。
如果兩者之間不直接發生聯絡,中間就需要一箇中間模組來負責兩者關係的處理
球員不關注球隊從哪來,只要給到我就行了。
這樣控制權就不是直接落在player這裡了,這正是IOC的設計思路。
依據IOC 改進
參照IOC的幾條原則,我們進行下改進。
-
高層模組不應該依賴低層模組。兩個都應該依賴抽象 這裡player是高層模組,直接依賴了球隊這個低階模組。所以我們將兩者解耦,player不再直接依賴於該team這個class
-
抽象不應該依賴具體實現,具體實現應該依賴抽象
具體到這裡來看我們的player模組不應該直接依賴具體team,而是通過建構函式將抽象的teaminfo例項傳遞進去,這樣就解耦具體實現。
直接看程式碼比較清楚:
// 球隊資訊不依賴具體實現
// 面向介面即面向抽象程式設計
class TeamInfo {
constructor(name) {
this.name = name
}
}
class Player {
// 此處的引數,是teamInfo的一個例項,不直接依賴具體的例項
// 面向抽象
constructor(team) {
this.team = team
}
info() {
console.log(this.team.name)
}
}
// 將依賴關係放到此處來管理,控制權也放到此處
// Player和TeamInfo之間不再有直接依賴
// 原本直接掌握teaminfo控制權的player不再直接依賴
// 將依賴控制,落在此處(第三方模組專門管理)即為控制反轉
var ym = new Player(new TeamInfo('火箭'))
ym.info()
var kobe = new Player(new TeamInfo('湖人'))
kobe.info()
複製程式碼
這裡發現,TeamInfo和Player之間已經沒有直接關聯了,依賴關係統一放到getTeamInfo中。
所謂控制反轉就如何上面一樣,將依賴的控制權由player轉移到其他地方即我們專門的依賴管理來做了。
這樣再增加一個team3,改動也不大,複用就行了。
其中之間的關係,如下面這個圖:
彼此不直接發生聯絡,依賴關係統一在中間模組來管理,更加清晰。
實現
上面其實就是最簡單的IOC實現了,基於IOC的程式設計思想,主要有兩種實現方式:依賴注入和依賴查詢。依賴查不太常用,常見的是依賴注入。
依賴注入
在js中常見的就是依賴注入。從名字上理解,所謂依賴注入,即元件之間的依賴關係由容器在執行期決定,形象的來說,即由容器動態的將某種依賴關係注入到元件之中。
在RequireJS/AMD的模組載入器的實現就是基於依賴注入來的,還有大名鼎鼎的angular,其實現也使用了大量的依賴注入。
結束語
關於控制反轉,一句話總結:控制反轉這裡控制權從使用者本身轉移到第三方容器上,而非是轉移到被呼叫者上,這裡需要明確不要疑惑。控制反轉是一種思想,依賴注入是一種設計模式。 可能聽起來比較抽象,其實我們平時開發中見到和用到的也是蠻多的,可能原來沒有對應起來罷了。 至於依賴注入,前端領域用到的就更多了,下面我將結合自身實踐翻譯一篇個人認為很好的文章Dependency-injection-in-JavaScript,來進一步深入依賴注入。 至此,個人見解分享完畢,拋磚引玉,希望共同學習進步。更多文章請移步我的部落格