提高可扩展性的目的
- 面对需求变更,方便需求更改
- 减少代码修改的难度
什么是好的扩展
- 需求的变更,不需要重写
- 代码修改不会引起大规模变动
- 方便加入新模块
提高可扩展性的设计模式
适配器模式(面向接口)
- 目的:通过写一个适配器,来代替替换
- 应用场景:接口不通用时
-
基本结构
// 用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());
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。