Thinking系列,旨在利用10分钟的时间传达一种可落地的编程思想。
系统中,经常会出现 A 模块,依赖 B 模块,同时也依赖 C 模块的情况。我们通常的处理方式是将 B、C 模块直接引入到 A 模块中,这个获取过程都在 A 模块中实现,随着业务的扩充,A 模块可能还需要 D、E、F… 等等模块,这将导致代码高度耦合并且难以维护和调试。且未来想要删除某模块,需要删除模块的代码,同时需要删除在 A 模块中的引用。
/* A.js */
import B from 'B'
import C from 'C'
import ...
IOC(Inversion Of Control):控制反转 组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
其背后的核心思想:针对接口编程,不针对实现编程!接口驱动,使得其可以供不同灵活的子类实现
IOC 有两种实现方式:依赖查找和依赖注入。
依赖查找(Dependency Lookup):容器提供接口和上下文条件给组件,组件需要实现接口,该接口提供了一个对象,可以重用这个对象查找依赖。
依赖注入(Dependency Injection):内置对象是通过注入的方式进行创建。依赖注入有两种实现方式:Setter方式(传值方式)和构造器方式(引用方式)。
页面刷新,刷新按钮隶属于 Header 模块,页面 A 中点击刷新按钮。 常规思路:需要再 Header 中依赖 A 页面,然后调用 A 模块的刷新方法。这里 Header 模块属于高层模块 ,A 页面属于低层模块。
/* Header */
import A from './A'
import B from './B'
现在如果需要在 B 页面增加刷新,则 Header 同时也要依赖 B 模块。整个流程会非常复杂,随着业务扩大,整体模块耦合度也会很大。
将 Header 模块修改为依赖接口,页面 A 和 页面 B 各自实现接口。大大降低了 Header 模块的修改几率。
Header 模块
<template>
...
<li class="refresh" @click="refreshTag">
<idss-icon-svg name="circle-refresh-outline"></idss-icon-svg>刷新
</li>
...
</template>
<script>
export default {
...
methods: {
refreshTag () {
let initFn = this.getComponents().init
initFn && (typeof initFn) === 'function' && initFn()
},
getComponents () {
// 这里this.$route 相当于了传统IOC中的容器角色
let matchedArray = this.$route.matched
return matchedArray[matchedArray.length - 1].instances.default.$parent.$refs['router-view']
}
}
}
</script>
这里我们抽象了接口 init
方法,每个模块各自实现 init()
即可。
A 模块
export default {
name: 'A',
methods: {
init () {
// A模块的刷新逻辑
}
}
}
// Header
import contianer
contianer.getInstance('name').refresh()
// contianer
let contianer = []
function register (name, vm) {}
function getInstance() ()
// A
import contianer
register('A', vm)
// B
import contianer
register('A', vm)
优势:模块之间靠接口规则约束,完全解耦。增加模块只需按要求注册和提供相关 refresh 方法即可;未来想删除 B 模块,同样只需将 B 模块代码删除即可,Header和Container模块无需做任何修改!