好程式設計師解析Web前端中的IoC是什麼

好程式設計師IT發表於2019-04-11

   好程式設計師 解析 Web 前端中的 IoC 是什麼 今天要為大家分享的文章就是關於對 web 前端中的 IoC 的解釋。 Web 前端技術越來越火,前端應用在不斷壯大的過程中,內部模組間的依賴可能也會隨之越來越複雜,模組間的 低複用性 導致應用 難以維護,不過我們可以藉助計算機領域的一些優秀的程式設計理念來一定程度上解決這些問題,下面我們就來一起看一看 IoC

web 前端中的 IoC 是什麼?

一、什麼是 IoC

IoC 的全稱叫做 Inversion of Control ,可翻譯為為「控制反轉」或「依賴倒置」,它主要包含了三個準則:

1 、高層次的模組不應該依賴於低層次的模組,它們都應該依賴於抽象

2 、抽象不應該依賴於具體實現,具體實現應該依賴於抽象

3 、面向介面程式設計 而不要面向實現程式設計

概念總是抽象的,所以下面將以一個例子來解釋上述的概念:

假設需要構建一款應用叫 App ,它包含一個路由模組 Router 和一個頁面監控模組 Track ,一開始可能會這麼實現:

// app.js

 

import Router from './modules/Router';

 

import Track from './modules/Track';

 

class App {

 

constructor(options) {

 

this.options = options;

 

this.router = new Router();

 

this.track = new Track();

 

this.init();

 

}

 

init() {

 

window.addEventListener('DOMContentLoaded', () => {

 

this.router.to('home');

 

this.track.tracking();

 

this.options.onReady();

 

});

 

}

 

}

 

// index.js

 

import App from 'path/to/App';

 

ew App({

 

onReady() {

 

// do something here...

 

},

 

});

嗯,看起來沒什麼問題,但是實際應用中需求是非常多變的,可能需要給路由新增功能 ( 比如實現 history 模式 ) 或者更新配置 ( 啟用 history, ew Router({ mode: 'history' })) 。這就不得不在 App 內部去修改這兩個模組,這是一個 INNER BREAKING 的操作,而對於之前測試透過了的 App 來說,也必須重新測試。

很明顯,這不是一個好的應用結構,高層次的模組 App 依賴了兩個低層次的模組 Router Track ,對低層次模組的修改都會影響高層次的模組 App 。那麼如何解決這個問題呢,解決方案就是接下來要講述的 依賴注入 (Dependency Injection)

二、依賴注入

所謂的依賴注入,簡單來說就是把高層模組所依賴的模組透過傳參的方式把依賴「注入」到模組內部,上面的程式碼可以透過依賴注入的方式改造成如下方式:

// app.js

 

class App {

 

constructor(options) {

 

this.options = options;

 

this.router = options.router;

 

this.track = options.track;

 

this.init();

 

}

 

init() {

 

window.addEventListener('DOMContentLoaded', () => {

 

this.router.to('home');

 

this.track.tracking();

 

this.options.onReady();

 

});

 

}

 

}

 

// index.js

 

import App from 'path/to/App';

 

import Router from './modules/Router';

 

import Track from './modules/Track';

 

ew App({

 

router: new Router(),

 

track: new Track(),

 

onReady() {

 

// do something here...

 

},

 

});

可以看到,透過依賴注入解決了上面所說的 INNER BREAKING 的問題,可以直接在 App 外部對各個模組進行修改而不影響內部。

是不是就萬事大吉了 ? 理想很豐滿,但現實卻是很骨感的,沒過兩天產品就給你提了一個新需求,給 App 新增一個分享模組 Share 。這樣的話又回到了上面所提到的 INNER BREAKING 的問題上:你不得不對 App 模組進行修改加上一行 this.share = options.share ,這明顯不是我們所期望的。

雖然 App 透過依賴注入的方式在一定程度上解耦了與其他幾個模組的依賴關係,但是還不夠徹底,其中的 this.router this.track 等屬性其實都還是對「具體實現」的依賴,明顯違背了 IoC 思想的準則,那如何進一步抽象 App 模組呢。

Talk is cheap, show you the code

 

class App {

 

static modules = []

 

constructor(options) {

 

this.options = options;

 

this.init();

 

}

 

init() {

 

window.addEventListener('DOMContentLoaded', () => {

 

this.initModules();

 

this.options.onReady(this);

 

});

 

}

 

static use(module) {

 

Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);

 

}

 

initModules() {

 

App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));

 

}

 

}

經過改造後 App 內已經沒有「具體實現」了,看不到任何業務程式碼了,那麼如何使用 App 來管理我們的依賴呢:

// modules/Router.js

 

import Router from 'path/to/Router';

 

export default {

 

init(app) {

 

app.router = new Router(app.options.router);

 

app.router.to('home');

 

}

 

};

 

// modules/Track.js

 

import Track from 'path/to/Track';

 

export default {

 

init(app) {

 

app.track = new Track(app.options.track);

 

app.track.tracking();

 

}

 

};

 

// index.js

 

import App from 'path/to/App';

 

import Router from './modules/Router';

 

import Track from './modules/Track';

 

App.use([Router, Track]);

 

ew App({

 

router: {

 

mode: 'history',

 

},

 

track: {

 

// ...

 

},

 

onReady(app) {

 

// app.options ...

 

},

 

});

可以發現 App 模組在使用上也非常的方便,透過 App.use() 方法來「注入」依賴,在 ./modules/some-module.js 中按照一定的「約定」去初始化相關配置,比如此時需要新增一個 Share 模組的話,無需到 App 內部去修改內容:

// modules/Share.js

 

import Share from 'path/to/Share';

 

export default {

 

init(app) {

 

app.share = new Share();

 

app.setShare = data => app.share.setShare(data);

 

}

 

};

 

// index.js

 

App.use(Share);

 

ew App({

 

// ...

 

onReady(app) {

 

app.setShare({

 

title: 'Hello IoC.',

 

description: 'description here...',

 

// some other data here...

 

});

 

}

 

});

直接在 App 外部去 use 這個 Share 模組即可,對模組的注入和配置極為方便。

那麼在 App 內部到底做了哪些工作呢,首先從 App.use 方法說起:

class App {

 

static modules = []

 

static use(module) {

 

Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);

 

}

 

}

可以很清楚的發現, App.use 做了一件非常簡單的事情,就是把依賴儲存在了 App.modules 屬性中,等待後續初始化模組的時候被呼叫。

接下來我們看一下模組初始化方法 this.initModules() 具體做了什麼事情:

class App {

 

initModules() {

 

App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));

 

}

 

}

可以發現該方法同樣做了一件非常簡單的事情,就是遍歷 App.modules 中所有的模組,判斷模組是否包含 init 屬性且該屬性必須是一個函式,如果判斷透過的話,該方法就會去執行模組的 init 方法並把 App 的例項 this 傳入其中,以便在模組中引用它。

從這個方法中可以看出,要實現一個可以被 App.use() 的模組,就必須滿足兩個「約定」:

1. 模組必須包含 init 屬性

2. init 必須是一個函式

這其實就是 IoC 思想中對「面向介面程式設計 而不要面向實現程式設計」這一準則的很好的體現。 App 不關心模組具體實現了什麼,只要滿足對 介面 init 的「約定」就可以了。

此時回去看 Router 的模組的實現就可以很容易理解為什麼要怎麼寫了:

// modules/Router.js

 

import Router from 'path/to/Router';

 

export default {

 

init(app) {

 

app.router = new Router(app.options.router);

 

app.router.to('home');

 

}

 

};

最後總結:

App 模組此時應該稱之為「容器」比較合適了,跟業務已經沒有任何關係了,它僅僅只是提供了一些方法來輔助管理注入的依賴和控制模組如何執行。

控制反轉 (Inversion of Control) 是一種「思想」,依賴注入 (Dependency Injection) 則是這一思想的一種具體「實現方式」,而這裡的 App 則是輔助依賴管理的一個「容器」。

 

   以上就是今天為大家 做的 分享,希望本篇文章能夠對正在從事 web 相關工作的小夥伴們有所幫助。想要了解更多 web 相關知識記得關注 好程式設計師 web 前端 培訓官網。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69913892/viewspace-2641070/,如需轉載,請註明出處,否則將追究法律責任。

相關文章