设计模式总结 | 青训营笔记

64 阅读6分钟

这是我参与「 第四届青训营 」笔记创作活动的第二天

23种设计模式

创建型 - 如何创建一个对象

工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式

结构型 - 如何灵活的将对象组装成较大的结构

适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

行为型 - 负责对象间的高效通信和职责划分

策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

设计模式的核心是‘观察整个逻辑里面的变与不变,然后将变与不变分离,达到使变化的部分灵活、不变的地方稳定的目的’

浏览器中的设计模式

  • 单例模式
  • 发布订阅模式

单例模式

定义:全局唯一访问对象(一般登录、购物车等都是一个单例)

应用场景:缓存,全局状态管理、浏览器的 window 对象等

作用:

  • 模块间通信
  • 保证某个类的对象的唯一性
  • 防止变量污染

注意:

  • 正确使用 this
  • 闭包容易造成内存泄漏,需及时清除不需要的变量
  • 创建一个新对象的成本较高
    // 单例对象
    class SingleObject {
        login () {}
    }
    // 访问方法
    SingleObject.getInstance = (function () {
        let instance;
        return function () {
            if (!instance) {
                instance = new SingleObject();
            }
            return instance;
        }
    })()
    const obj1 = SingleObject.getInstance();
    const obj2 = SingleObject.getInstance();
    console.log(obj1 === obj2); // true
    //可以通过 SingleObject.getInstance() 来获取到单例,并且每次调用均获得到同一个单例

发布订阅模式(≠观察者模式)

定义:一种订阅机制,可在被订阅对象发生变化时通知订阅者

消息的发布者,不会将消息直接发送给特定的订阅者,而是通过消息通道广播出去,订阅者通过订阅获取到想要的消息。

应用场景:从系统架构之间的解耦,到业务中的一些实现模式,像邮件订阅,上线订阅,系统消息通知,网站日志记录,JavaScript 事件机制,react/vue 等的观察者等等

使用条件:

  • 各模块相互独立
  • 存在一对多的依赖关系
  • 依赖模块不稳定、依赖关系不稳定(避免对象间的紧密耦合)
  • 各模块由不同的人员、团队开发

注意:要先监听,再触发

Javascript 中的设计模式

  • 工厂模式
  • 适配器模式
  • 装饰器模式
  • 原型模式
  • 代理模式
  • 外观模式
  • 迭代器模式
  • 策略模式

工厂模式

定义:相当于创建实例对象的 new,提供一个创建对象的接口

应用场景:JQuery 中的$、Vue.component 异步组件、React.createElement 等

  • 对象的构建十分复杂
  • 需要依赖具体环境创建不同实例
  • 处理大量具有相同属性的小对象
    // 某个需要创建的具体对象
    class Product {
        constructor (name) {
            this.name = name;
        }
        init () {}
    }
    // 工厂对象
    class Creator {
        create (name) {
            return new Product(name);
        }
    }
    const creator = new Creator();
    const p = creator.create(); // 通过工厂对象创建出来的具体对象

适配器模式

定义:用来解决两个接口不兼容的问题,由一个对象来包装不兼容的对象(比如参数转换,允许直接访问)

应用场景:Vue 中的 computed、旧的 JSON 格式转换成新的格式等

装饰器模式

定义:在不改变对象自身的基础上,动态的给某个对象添加新的功能,同时又不改变其接口

应用场景:ES7 装饰器、Vuex 中1.0版本混入 Vue 时,重写 init 方法、Vue 中数组变异方法实现等

原型模式

定义:复制已有对象来创建新的对象

应用场景:JS 中对象创建的基本模式

代理模式

定义:可自定义控制对原对象的访问方式,并且允许在更新前后做一些额外处理(为其他对象提供一种代理,便以控制对这个对象的访问,不能直接访问目标对象)

应用场景:监控,代理工具,前端框架实现,ES6 Proxy,Vuex 中对于 getters 访问、图片预加载等等

作用:

  • 远程代理(一个对象对另一个对象的局部代理)
  • 虚拟代理(对于需要创建开销很大的对象如渲染网页大图时,可以先用缩略图代替真图)
  • 安全代理(保护真实对象的访问权限)
  • 缓存代理(一些开销较大的运算提供暂时的存储,下次运算时,如果传递进来的参数跟之前的相同,则可以直接返回前面存储的运算结果)
// 缓存代理
function sum(a, b){
  return a + b
}
let proxySum = (function(){
  let cache = {}
  return function(){
      let args = Array.prototype.join.call(arguments, ',');
      if(args in cache){
          return cache[args];
      }

      cache[args] = sum.apply(this, arguments)
      return cache[args]
  }
})()

外观模式

定义:为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口的访问更容易,不符合单一职责和开放封闭原则(一个函数封装了复杂的操作)

应用场景:JS 事件不同浏览器兼容处理、同一方法可以传入不同参数兼容处理等

作用:

  • 对接口和调用者进行了一定的解耦
  • 创造经典的三层结构 MVC
  • 在开发阶段减少不同子系统之间的依赖和耦合,方便各个子系统的迭代和扩展
  • 为大型复杂系统提供一个清晰的接口

注意:外观模式被开发者连续调用时会造成一定的性能损耗(由于每次调用都会进行可用性检测)

//兼容不同浏览器的事件监听函数
function on(type, fn){
  // 对于支持dom2级事件处理程序
  if(document.addEventListener){
      dom.addEventListener(type,fn,false);
  }else if(dom.attachEvent){
  // 对于IE9一下的ie浏览器
      dom.attachEvent('on'+type,fn);
  }else {
      dom['on'+ type] = fn;
  }
}

迭代器模式

定义:在不暴露数据类型的情况下访问集合中的数据(提供一种方法顺序访问一个聚合对象中的各个元素,使用者并不需要关心该方法的内部表示)

应用场景:数据结构中有多种数据类型,列表,树等,提供通用操作接口(forEach、map、reduce等)

作用:

  • 为遍历不同集合提供统一的接口
  • 保护原集合但又提供外部访问内部元素的方式
//遍历函数(不仅可以遍历数组和字符串,还可以遍历对象)
function _each(el, fn = (v, k, el) => {}) {
  // 判断数据类型
  function checkType(target){
    return Object.prototype.toString.call(target).slice(8,-1)
  }

  // 数组或者字符串
  if(['Array', 'String'].indexOf(checkType(el)) > -1) {
    for(let i=0, len = el.length; i< len; i++) {
      fn(el[i], i, el)
    }
  }else if(checkType(el) === 'Object') {
    for(let key in el) {
      fn(el[key], key, el)
    }
  }
}

策略模式

定义:将不同算法进行合理的分类和单独封装,让不同算法之间可以互相替换而不会影响到算法的使用者

应用场景:实现更优雅的表单验证,游戏里的角色计分器,棋牌类游戏的输赢算法,微信抢红包算法等

作用:

  • 实现不同,作用一致
  • 调用方法相同,降低了使用成本以及不同算法之间的耦合
  • 单独定义算法模型,方便单元测试
  • 避免大量冗余的代码判断(比如 if else)
//根据不同类型实现求和算法
const obj = {
  A: (num) => num * 4,
  B: (num) => num * 6,
  C: (num) => num * 8
}

const getSum =function(type, num) {
  return obj[type](num)
}

前端框架中的设计模式

  • 代理模式
  • 组合模式

代理模式

组合模式

定义:可多个对象组合使用,也可单个对象独立使用

应用场景:DOM,前端组件,文件目录,部门