随着项目需求的增长,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())
复制代码
依赖耦合永远都是咱们在写业务上必需要解决的问题,不管是服务端的模块化仍是前端的组件化,下降依赖和让模块更稳定,更独立,实现关注点分离。