提高可扩展性的目的

  • 面对需求变更,方便需求更改
  • 减少代码修改的难度

什么是好的扩展

  1. 需求的变更,不需要重写
  2. 代码修改不会引起大规模变动
  3. 方便加入新模块

提高可扩展性的设计模式

适配器模式(面向接口)

  • 目的:通过写一个适配器,来代替替换
  • 应用场景:接口不通用时
  • 基本结构

    // 用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() {}}// 新建方法mybfunction 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.prototypelet 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)}})()
    1. 用户只需关心输入的命令,不需要关心API
    2. 命令和实现解耦
    • 一般情况下 数据 =》 调用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());