设计模式

Design pattern


  • 设计:设计原则(统一指导思想)
  • 模式:通过概念总结出的一些模板,可以效仿的固定式的东西(根据指导思想结合开发经验,总结出固定的样式或模板)

在设计一些设计模式时,一般遵循如下七项基本原则:

  1. 单一职责原则 (Single Responsibility Principle)
    • 一个对象或方法只做一件事情。
    • 如果功能过于复杂就拆分开,每个部分保持独立
    • 应该把对象或方法划分成较小的粒度,提高代码可读性,提高系统可维护性。
  2. 开放-关闭原则 (Open-Closed Principle)
    • 对扩展开放:有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
    • 对修改封闭:一旦设计完成,模块的源代码不能被侵犯,任何人不允许修改已有源代码。
    • 开放封闭的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。
    • 让类依赖于固定的抽象,所以对修改就是封闭的;
    • 而通过面向对象的继承和多态机制,可以实现对抽象体的继承,通过覆写其方法来改变固有行为,实现新的扩展方法,所以对于扩展就是开放的。
  3. 里氏替换原则 (Liskov Substitution Principle)
    • 子类能覆盖父类
    • 父类能出现的地方子类就能出现
    • js中使用功能较少(弱类型 & 继承使用较少)
  4. 依赖倒转原则 (Dependence Inversion Principle)
    • 面向接口编程,依赖于抽象而不依赖于具体
    • 使用方只关注接口而不关注具体类的实现
    • js中使用较少(没有接口概念,弱类型)
  5. 接口隔离原则 (Interface Segregation Principle)
    • 保持接口的单一独立
    • js中没有接口概念(typescript例外)
    • 类似单一职责原则,这里更关注接口
  6. 最少知道原则(The Least Knowledge Principle)
    • 它描述了一种保持代码松耦合的策略,每个单元对其他单元只拥有有限的知识,只了解与当前单元紧密联系的单元;
    • 现代面向对象程序设计语言通常使用 “.” 作为访问标识,LoD 可以被简化为 “仅使用一个点(use only one dot)”。
    • 也就是说,代码 a.b.Method() 违反了 LoD,而 a.Method() 则符合 LoD。
      打个比方,人可以命令一条狗行走,但是不应该直接指挥狗的腿行走,应该由狗去指挥它的腿行走。
  7. 组合/聚合复用原则 (Composite/Aggregate Reuse Principle)

上述原则1、2在js中应用较多;以promise为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function loadImg() {
return new Promise((resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
reject()
}
})
}

// 1、这里就体现了单一原则,每个then只做一件事
// 2、开放封闭原则,如果有新的需求加第三个then即可
loadImg().then(img => {
return img // return img的意思是下一个then也需要img参数
}).then(img => {
//
}).catch()

按类型分

创建型(对象的创建及生成)

  • 工厂模式(实例化对象)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 频繁创建类似对象时可用此模式
    // 例:工厂生产产品
    class Product { // 产品即需要频繁创建的对象
    constructor(name) {
    this.name = name
    }
    s() {}
    o() {}
    ...
    }

    class Creator { // 工厂
    constructor() {}
    create(name) {
    return new Product(name)
    }
    }

    const creator = new Creator() // 生成工厂

    const p1 = creator('汉堡')
    const p2 = creator('薯条')
  • 单例模式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    // 只实例化一次,之后一直使用第一次实例化对象
    // 例:单例弹框

    class Dialog {
    constructor() {
    this.dislogDom = document.createElement('div')
    this.isShow = false
    this.content = ''
    }
    handleShow(content) {
    this.content = content
    this.dislogDom.innerText = this.content
    if (this.isShow) return
    this.isShow = true
    document.body.appendChild(this.dislogDom)
    }
    handleClose() {
    if(!this.isShow) return
    this.isShow = false
    document.body.removeChild(this.dislogDom)
    }
    }
    // 闭包+静态方法实现单例
    Dialog.getInstance = (function() {
    let instance
    return function() {
    if(!instance) {
    instance = new Dialog()
    }
    return instance
    }
    })()

    const myDialog1 = Dialog.getInstance()
    myDialog1.handleShow('1111')
    setTimeout(() => {
    const myDialog2 = Dialog.getInstance()
    myDialog1.handleShow('222')
    console.log(myDialog1 === myDialog2) // true
    }, 2000);

    // 代理实现单例
    const proxyDialog = (function() {
    let ins
    return function() {
    return ins || (ins = new Dialog())
    }
    })()
    // d1 === d2
    const d1 = new proxyDialog()
    const d2 = new proxyDialog()

    // 抽象getInstance,封装实现通用方法
    const getInstance = function(fn) {
    let ins
    return () => {
    return ins || (ins = fn.call({}, arguments)) // 通过函数调用传入
    // return ins || (ins = new fn()) 如果通过class传入需要new
    }
    }

    const Dialog = function () {
    this.dislogDom = document.createElement('div')
    this.isShow = false
    this.content = ''
    this.handleShow = (content) => {
    this.content = content
    this.dislogDom.innerText = this.content
    if (this.isShow) return
    this.isShow = true
    document.body.appendChild(this.dislogDom)
    }
    this.handleClose = () => {
    if(!this.isShow) return
    this.isShow = false
    document.body.removeChild(this.dislogDom)
    }
    return this
    }

    const cd = getInstance(Dialog) // 调用形成闭包
    const cdOther = getInstance(Other)
    const d1 = cd() // 之后操作闭包内函数即可实现单例
    const d2 = cd()
    console.log(d1 === d2) // true
  • 原型模式
    1
    2
    3
    4
    5
    6
    7
    8
    // 原型模式是一种用于创建对象的模式,不同于用类创建,原型模式使用克隆对象的方式来创建一个新的对象

    function Fn() {
    this.name = '123'
    }

    var fn = new Fn()
    var cloneFn = Object.create(fn)

    组合型(对象和类是怎样的组合形式;一个类不一定能满足需求,通过组合的形式完成)

  • 适配器模式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实 现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够 使它们协同作用
    // 通过包装函数,统一接口定义

    var googleMap = {
    show: function(){
    console.log( '开始渲染谷歌地图' );
    }
    };

    var baiduMap = {
    display: function(){
    console.log( '开始渲染百度地图' );
    }
    };

    // 两种地图接口不同(调用方法不同),为了统一,使用适配器在百度地图外层套一层,实现统一

    var baiduMapAdapter = {
    show: function(){
    return baiduMap.display();
    }
    };

  • 桥接模式
  • 装饰器模式(既能使用原有的功能,又能使用装饰后的功能)
  • 组合模式
  • 代理模式
  • 享元模式
  • 外观模式

    行为型(涵盖了开发中的一些常用行为,如何设计才能满足需求)

  • 策略模式
  • 迭代器模式
  • 模板方法模式
  • 职责连模式
  • 观察者模式
  • 命令模式
  • 备忘录模式
  • 中介者模式
  • 状态模式
  • 解释器模式
  • 访问者模式

参考:
JS设计模式(全)
web前端进阶之js设计模式篇——上
详解 Javascript十大常用设计模式
js设计模式【详解】总目录——最常用的20种设计模式