前端解讀控制反轉(IOC)

瀟湘待雨發表於2018-10-24

前言

隨著前端承擔的職責越來越重,前端應用向著複雜化、規模化的方向發展。大型專案模組化是一種趨勢,不可避免模組之間要相互依賴,此外還有很多第三方包。這樣的話如何去管理這些繁雜的檔案,是一個不可避免的話題。此時作為一種已經被實踐證明過的思想模式一直得到大家的青睞,這就是控制反轉(IOC)。

IOC定義

先看一下維基百科上的定義:
控制反轉(Inversion of Control,縮寫為IoC),是物件導向程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。

原則

  1. 高層模組不應該依賴低層模組。兩個都應該依賴抽象
  2. 抽象不應該依賴具體實現
  3. 面向介面程式設計,而非面向實現程式設計

針對前端來說,介面的概念不那麼清晰明瞭,不像強型別語言。 概念是比較枯燥的,下面結合例子來看一下可能更好理解一點。

目的

根據概念可以看到最主要的目的就是降低耦合,提高擴充套件性。在深究之前,我們先看下程式碼耦合

程式碼耦合

所謂耦合,可以如下圖顯示: 前端解讀控制反轉(IOC) 比較清晰明瞭,程式碼相互之間的聯絡太直接: 假如obj2報錯,那麼整個系統也都報錯了。
所以我們的目的就是降低二者之間的耦合度,
結合圖來說比較清晰,
如果兩者不這麼直接的發生關係,那麼相互影響的概率就小了那麼多了。

另外,這是比較少的模組,常規專案裡顯然不僅僅是隻有這麼少,想象一下多個模組的場景: 前端解讀控制反轉(IOC) 這裡除了耦合之外,不同齒輪之間的依賴關係也是個頭疼的問題,迭代個幾個版本之後發現,這是什麼東西,一動就有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的幾條原則,我們進行下改進。

  1. 高層模組不應該依賴低層模組。兩個都應該依賴抽象 這裡player是高層模組,直接依賴了球隊這個低階模組。所以我們將兩者解耦,player不再直接依賴於該team這個class

  2. 抽象不應該依賴具體實現,具體實現應該依賴抽象
    具體到這裡來看我們的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實現了,基於IOC的程式設計思想,主要有兩種實現方式:依賴注入和依賴查詢。依賴查不太常用,常見的是依賴注入。

依賴注入

在js中常見的就是依賴注入。從名字上理解,所謂依賴注入,即元件之間的依賴關係由容器在執行期決定,形象的來說,即由容器動態的將某種依賴關係注入到元件之中。

在RequireJS/AMD的模組載入器的實現就是基於依賴注入來的,還有大名鼎鼎的angular,其實現也使用了大量的依賴注入。

結束語

關於控制反轉,一句話總結:控制反轉這裡控制權從使用者本身轉移到第三方容器上,而非是轉移到被呼叫者上,這裡需要明確不要疑惑。控制反轉是一種思想,依賴注入是一種設計模式。 可能聽起來比較抽象,其實我們平時開發中見到和用到的也是蠻多的,可能原來沒有對應起來罷了。 至於依賴注入,前端領域用到的就更多了,下面我將結合自身實踐翻譯一篇個人認為很好的文章Dependency-injection-in-JavaScript,來進一步深入依賴注入。 至此,個人見解分享完畢,拋磚引玉,希望共同學習進步。更多文章請移步我的部落格

相關文章