隨着項目需求的增長,Node 模塊也相繼增長,各模塊間的依賴耦合度愈來愈嚴重,很是難維護,有時候改一處代碼須要懂好幾處代碼,項目逐漸達到牽一髮而動全身的地步,所謂高內聚、低耦合
徹底沒得談,如何作到模塊間高度解耦是每一個工程師必要思考的問題。前端
爲了解決模塊間依賴耦合的問題,最好的辦法就是要找到一種設計模式來幫助咱們作到模塊間解耦。其中作的很好的兩個就是 DI 和 IOC 了,下面來探討一下這兩種設計模式。數據庫
Dependency Injection
簡稱DI
,中文是依賴注入的意思。即模塊之間的依賴由高層模塊在運行期決定,形象的說,當高層模塊在運行時肯定須要哪一個底層模塊,而且注入進去。來看下面 🌰。設計模式
noDI服務器
這裏有兩個類,一個是訂單模塊類,一個是訂單控制器類。app
// 訂單model類 models/Order.js
class Order {
constructor() {}
insert() {
//......數據庫操做
return true
}
}
// 訂單類 controllers/OrderController.js
const Order = require('./Order.js')
class OrderController {
constructor() {
this.order = new Order()
}
craeteOrder(...args) {
this.order.insert(...args)
}
}
//router/index.js
const OrderController = require('../controllers/OrderController.js')
const orderController = new OrderController()
複製代碼
上面是沒有依賴注入的狀況,能夠看出OrderController
類嚴重耦合了Order
類。上面的OrderController
類依賴了Order
類,因此在使用的時候就必須先require
Order
類,才能夠在OrderController
類中使用。耦合性過高,假如咱們須要把Order
類文件移動到了別的目錄,那麼全部依賴這個類的文件都須要變化。koa
DI模塊化
沿用上面兩個類,咱們來看一下依賴注入的狀況。函數
// 訂單model類 models/Order.js
class Order {
constructor() {}
insert() {
//......數據庫操做
return true
}
}
// 訂單類 controllers/OrderController.js
class OrderController {
constructor(order) {
this.order = new order()
}
craeteOrder(...args) {
this.order.insert(...args)
}
}
//router/index.js
const Order = require('../models/Order.js')
const OrderController = require('../controllers/OrderController.js')
const orderController = new OrderController(new Order())
複製代碼
從上面代碼來看OrderController
類文件中已經不須要手動的引入Order
類了,而是經過 constructor 在運行時的時候傳進去。在 router 文件中,當實例化OrderController
類的時候,同時也實例化Order
類,而且做爲OrderController
構造函數的參數傳進去。組件化
能夠看出依賴注入已經讓咱們模塊間解耦,可是仍是有點不足之處,下面總結一下依賴注入的優勢與不足。post
優勢
經過依賴注入的方式,是咱們高層模塊和底層模塊間的耦合下降了,所以,當底層模塊位置變化的時候,咱們只須要懂 router 中的依賴路徑就能夠了,高層模塊中作了什麼咱們都不須要關心了。
不足
能夠看出咱們全部的依賴的模塊都是在 router 模塊中引入的,明顯的下降了 router 模塊的複雜性。咱們須要一個用來專門管理注入方以及被注入方的容器,讓 router 模塊和往常同樣輕量,沒錯這個東西就是 IOC。
Inversion of Control
簡稱IOC
,即控制反轉,並非什麼技術,而是一種設計模式。上面提到 DI 的不足可使用 IOC 來解決,其實 IOC 也叫 DI。IOC 意味着將依賴模塊和被依賴模塊都交給容器去管理,當咱們使用模塊的時候,由容器動態的將它的依賴關係注入到模塊中去。依據上面 DI 的思想,下面就來實現如下 IOC。
首先 IOC 中有個容器的概念,用來管理全部模塊。
class IOC {
constructor() {
this.container = new Map()
}
}
複製代碼
咱們要有一個方法,用於讓咱們往容器中存入模塊,例如上面 Order 類,IOC 必需要有這麼一個方法。
const Order = require('../models/Order.js')
const OrderController = require('../controllers/OrderController.js')
ioc.bind('order', (...args) => new OrderController(new Order(...args)))
複製代碼
bind 方法用於往 IOC 容器中存放模塊間的依賴,並此刻肯定高層模塊的依賴項。
class IOC {
constructor() {
this.container = new Map()
}
bind(key, callback) {
this.container.set(key, { callback, single: false })
}
}
複製代碼
上面就是 bind 方法很簡單,你會發現我在容器的每一項上添加了 single 屬性,這是用來標識有些類時單例的。下面來實現如下單例的寫法。
class IOC {
constructor() {
this.container = new Map()
}
bind(key, callback) {
this.container.set(key, { callback, single: false })
}
singleton(key, callback) {
this.container.set(key, { callback, single: true })
}
}
複製代碼
當模塊放入容器中的時候,咱們須要用的時候怎麼辦呢?因此必需要有一個方法用於獲取到容器中的模塊。
//router.js
const ioc = require('ioc')
const orderController = ioc.use('order')
複製代碼
上面經過 use 方法就能夠獲取到了。下面是 use 方法的實現
class IOC {
constructor() {
this.container = new Map()
}
bind(key, callback) {
this.container.set(key, { callback, single: false })
}
singleton(key, callback) {
this.container.set(key, { callback, single: true })
}
use(key) {
const item = this.container.get(key)
if (!item) {
throw new Error('error')
}
if (item.single && !item.instance) {
item.instance = item.callback()
}
return item.single ? item.instance : item.callback()
}
}
複製代碼
上面代碼就是 use 方法的實現,首先經過 key 值在容器中找到對應的模塊,判斷若是模塊不存在則報錯,而後判斷是不是單例,若是是單例判斷是否已經被實例化,已經實例化就不須要再進行,最後若是是單例的話返回單例實例,則執行 callback 實例化。
以上的代碼徹底能夠作到 IOC 的功能。
運行在服務器上的代碼,當咱們去作測試的時候,確定不能直接使用運行時的代碼。因此咱們給 IOC 添油加醋,作一個測試所用的容器。
class IOC {
constructor() {
this.container = new Map()
this.fakes = new Map()
}
bind(key, callback) {
this.container.set(key, { callback, single: false })
}
singleton(key, callback) {
this.container.set(key, { callback, single: true })
}
fake(key, callback) {
this.fakes.set(key, { callback, single: false })
}
restore(key) {
this.fakes.delete(key)
}
findInContainer(key) {
if (this.fakes.has(key)) {
return this.fakes.get(key)
}
return this.container.get(key)
}
use(key) {
const item = this.findInContainer(key)
if (!item) {
throw new Error('error')
}
if (item.single && !item.instance) {
item.instance = item.callback()
}
return item.single ? item.instance : item.callback()
}
}
複製代碼
上面添加了三種方法:fake
、restore
、findInContainer
最後咱們經過訂單類 🌰 來實踐一下使用 IOC 的狀況。
首先定義一個 constants 文件用於存在全部的 key 值
const TYPES = {
order: Symbol.for('order')
}
module.exports = {
TYPES
}
複製代碼
而後建立一個 orderIOC 文件作 IOC 的註冊中心
const IOC = require('ioc')
const Order = require('../models/Order.js')
const OrderController = require('../controllers/OrderController.js')
const { TYPES } = require('../constants')
const ioc = new IOC()
ioc.bind(TYPES.order, (...args) => new OrderController(new Order(...args)))
module.exports = ioc
複製代碼
在 router 文件中經過 IOC 來獲取到 OrderController 類的實例,以 koa 爲例
const Router = require('koa-router')
const ioc = require('../ioc')
const { TYPES } = require('../constants')
const router = new Router()
const orderController = ioc.use(TYPES.order)
router.post('/create', orderController.create)
module.exports = app => app.use(router.routes()).use(router.allowedMethods())
複製代碼
依賴耦合永遠都是咱們在寫業務上必需要解決的問題,不管是服務端的模塊化仍是前端的組件化,下降依賴和讓模塊更穩定,更獨立,實現關注點分離。