提高可扩展性的目的
- 面对需求变更,方便需求更改
- 减少代码修改的难度
什么是好的扩展
- 需求的变更,不需要重写
- 代码修改不会引起大规模变动
- 方便加入新模块
提高可扩展性的设计模式
适配器模式(面向接口)
- 目的:通过写一个适配器,来代替替换
- 应用场景:接口不通用时
-
基本结构
// 用 log 代替 console.log 即调用的接口改变 var log = (function() {return window.console.log})()
-
例 1:项目中本来用的 A 框架,现在要改成非常相似的 jQuery 框架,仅有部分接口名称不同
A.c() window.A = $ A.c = function () {return $.css.apply(this, arguments) }
-
例 2:当方法传入的参数较为复杂时,加上参数适配器,即默认值,用户传入的配置可以覆盖默认值
function fn (config) { let _default = {name: 1} for (let k in config) {_default[k] = config[k] || _default[k] } }
装饰者模式(面向方法)
- 目的:不重写方法,但能实现扩展方法
- 应用场景:当一个方法需要扩展,但是又不好直接去修改时
-
基本结构
// 现有模块 a,内部有方法 b,不能直接修改方法 b,但是需要扩展它 var a = {b: function() {}} // 新建方法 myb function myb () { // 先调用 a.b a.b() // 再添加需要扩展的部分 }
-
例 1 扩展已有的事件绑定
// 假设 dom 原本有 click 方法,需要在该方法中新增处理 document.getElementById('box').onclick = () => {console.log(1) } var decorator = function (dom, fn) {if (typeof dom.click === 'function') { let _oldFn = dom.onclick dom.onclick = function () {_oldFn() // 新增 fn()} } } decorator(document.getElementById('box'), () => {console.log(2) })
-
例 2 Vue 的数组监听
// 需求:vue defineProperty 监听数据变化,数组变化如何触发 // 装饰数组的方法 let arrayProto = Array.prototype let arrayMethods = Object.create(arrayProto) let methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach((method) => {var original = arrayProto[method] var result = original.apply(this, arguments) dep.notify() // 详细可查看 vue 源码 return result })
命令模式
- 目的:解耦实现和调用,让双方互不干扰
- 应用场景:调用的命令充满不确定性
-
基本结构
var command = (function() {var action = {} return function excute() {} })()
-
例 1 封装 canvas 绘图命令
// 实现 let mycanvas = () => {} mycanvas.prototype.drawCircle = () => {} mycanvas.prototype.drawRect = () => {} // 直接调用 调用和实现强耦合 let myC = new mycanvas() myC.drawCircle() myC.drawRect() // 命令模式实现 var canvasCommand = (function () { var action = {drawCircle () {}, drawRect () {} } return function excute (commander) { commander.forEach(item => {action[item.command](item.config) }); } })() // 通过传入配置项即可绘制图形 canvasCommand([{command: 'drawRect', config: {}}, {command: 'drawCircle', config: {}}, ])
-
例 2 绘制数量和排列顺序随机的图片
var canvasCommand = (function () { var action = {create(obj) {var _htmlArr = [] var _htmlStr = '' // 模板字符串,将需要替换的值使用特定的符号表示,之后进行替换 var _htmlTemplate = '<div><img src="{{img-url}}"/></div><h2>{{title}}</h2>' // 排序方式处理 var displayWay = {normal(arr) {return arr}, reverse() {return arr.reverse() } } obj.imgArr.forEach((item) => {var _html = ''_html = _htmlTemplate.replace('{{img-url}}', item.imgUrl).replace('{{title}}', item.title) _htmlArr.push(_html) }) _htmlArr = displayWay[obj.type](_htmlArr) _htmlStr = _htmlArr.join('') return "<div>" + _htmlStr + "</div>" }, display(obj) {obj.target.innerHTML = this.create(obj) } } return function excute(obj) { var _default = { imgArr: [ { imgUrl: 'xxx', title: 'default title' } ], type: 'normal', // 图片排序方式 target: document.body // 最终插入的 DOM 节点 } for (var item in _default) {_default[item] = obj[item] || _default[item] } action.display(_default) } })()
- 用户只需关心输入的命令,不需要关心 API
- 命令和实现解耦
- 一般情况下 数据 =》调用 API
- 命令模式下 数据 =》excute 命令解析层 => 调用 API
- 当数据变动时,在命令解析层进行处理即可,不需要去修改具体实现中代码,API 改动也一样
观察者模式
- 目的:减少对象间的耦合,提高扩展性
- 应用:当两个模块直接沟通会增加他们的耦合性时
-
基本结构:
function observe() {this.message = {} } // 注册 observe.prototype.register = function (type, fn) {this.message[type] = fn } // 触发 observe.prototype.fire = function (type) {this.message[type]()} // 销毁 observe.prototype.remove = function (type) {this.message[type] = null } var observeObj = new observe()
-
例 1:多人合作,A 写了首页模块,B 写了评论模块,需要将评论展示在首页
// 评论模块 function comment() { var self = this this.commentList = [ { type: 'normal', content: 'xxxxx' } ] // 注册事件 observeObj.register('indexComment', function () {var _arr = [] self.commentList.forEach((item) => {if (item.type == 'hot') {_arr.push(_item) } }) return _arr; }) } // 首页模块 function index() { // 触发事件 observeObj.fire('indexComment') }
- 例 2:转盘,每转一圈,速度加快!待看,并且改造九宫格抽奖
职责链模式
- 避免发送者和多个请求处理者耦合在一起
- 应用:把操作分割成一系列模块,每个模块只处理自己的事情,,形成一个链条,类似生产流水线
-
基本结构
function mode1() {} function mode2() {} function mode3() {} let _result _result = mode1(_result) _result = mode2(_result) _result = mode3(_result)
-
例 1:axios 拦截器的设计
function Axios () { this.interceptors = {request: new interceptorsManner(), response: new interceptorsManner(),} } Axios.prototype.request = function (config) { // dispatchEvent 实际发送请求的函数 var chain = [dispatchEvent, undefined] var promise = Promise.resolve(config) this.interceptors.request.handlers.forEach((interceptor) => {chain.unshift(interceptor.fullfilled, interceptor.rejected) }) this.interceptors.response.handlers.forEach((interceptor) => {chain.push(interceptor.fullfilled, interceptor.rejected) }) while(chain.length) {promise = promise.then(chain.shift(), chain.shift()) } return promise } function interceptorsManner(){this.handlers = [] } interceptorsManner.prototype.use = function (fullfilled, rejected) { this.handlers.push({ fullfilled, rejected }) }
-
例 2:数据需要同步、异步多次处理
// value 需要处理的值,arr 处理方法集合 function handler (value, arr) { let _result = value async function test () {while(arr.length) {_result = await arr.shift()(_result) } return _result } test().then((res) => {console.log(res) }) } handler(1, [function (value) {console.log(1) return value + 'hello' }, function (value) {console.log(2) return new Promise((resolve, reject) => {console.log(3) setTimeout(() => {resolve(value + 'async') }, 500) }) }, function (value) {console.log(4) return value + 'sync' } ])
访问者模式(较少使用)
- 目的:解耦数据结构与数据的操作
- 应用:数据结构不希望与操作有关联
-
基本结构
var data = [] var handler = function () {} handler.prototype.get = function () {} var visitor = function (handler, data) {handler.get(data) }
-
例:不同角色访问数据,财务报表,财务关心支出和收入,老板关心盈利
function report() { this.income = ""; this.cost = ""; this.profit = ""; } function boss() {} boss.prototype.get = function (profit) {} function finance() {} finance.prototype.get = function (income, cost) {} function vistor(data, man) { var handle = {boss: function (data) {man.get(data.profit); }, finance: function (data) {finance.get(data.income, data.cost); } } handle[man.constructor.name](data); } vistor(new report(), new boss());