2

提高可扩展性的目的

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

什么是好的扩展

  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() {}
    }
    // 新建方法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)
    }
    })()
    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());

CSep27
37 声望1 粉丝

学习中...整理中...