设计模式:设计模式笔记

每次都忘记一些设计模式,这里记录一下常用的一些。

设计模式

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结,是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式原则

  • Single Responsibility Principle 单一职责原则 (S)

    • 一个程序只做好一件事
    • 如果功能过于复杂就拆分开,每个部分保持独立
  • OpenClosed Principle 开放/封闭原则 (O)

    • 对扩展开放,对修改封闭
    • 增加需求时,扩展新代码,而非修改已有代码
  • Liskov Substitution Principle 里氏替换原则 (L)

    • 子类能覆盖父类
    • 父类能出现的地方子类就能出现
  • Interface Segregation Principle 接口隔离原则 (I)

    • 保持接口的单一独立
    • 类似单一职责原则,这里更关注接口
  • Dependency Inversion Principle 依赖倒转原则 (D)

    • 面向接口编程,依赖于抽象而不依赖于具体
    • 使用方只关注接口而不关注具体类的实现

SO 体现较多,举个栗子:(比如 Promise)
单一职责原则:每个 then 中的逻辑只做好一件事
开放封闭原则(对扩展开放,对修改封闭):如果新增需求,扩展 then

设计模式分类

单例模式

单例模式两个条件:

  • 确保只有一个实例
  • 可以全局访问

实例:

  • 全局对象如 window、global 等
  • 例如 Vue 也作为单例
  • 弹窗组件,当需要时创建,关闭时缓存,再次调用时重复打开,而不是重新创建,徒损性能。

遇到的坑:

  • 布局包、amis 包,依赖权限包,权限包请求并保存了权限 code。但是布局包的权限包与实际权限包版本号不统一,生成了两个实例。peerDependncy

策略模式

  • 根据不同参数可以命中不同的策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const strategy = {
S: function (salary) {
return salary * 4
},
A: function (salary) {
return salary * 3
},
B: function (salary) {
return salary * 2
},
}

const calculateBonus = function (level, salary) {
return strategy[level](salary)
}

calculateBonus('A', 10000) // 30000

代理模式

  • 为一个对象提供一个代用品或占位符,以便控制对它的访问。

场景:

  • Proxy 或者 Object.defineProperty

迭代器模式

  • 提供一种方法顺序一个聚合对象中各个元素,而又不暴露该对象的内部表示。

实例:

  • Array.prototype.forEach
  • ES6 Iterator

发布-订阅模式 & 观察者模式

定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使它们能够自动更新自己,当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。

发布订阅模式与观察者模式都是感知变化,相应变化。区别在于有没有中间人。

代码:

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
// 主题 保存状态,状态变化之后触发所有观察者对象
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState() {
return this.state
}
setState(state) {
this.state = state
this.notifyAllObservers()
}
notifyAllObservers() {
this.observers.forEach((observer) => {
observer.update()
})
}
attach(observer) {
this.observers.push(observer)
}
}

// 观察者
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this)
}
update() {
console.log(`${this.name} update, state: ${this.subject.getState()}`)
}
}

// 测试
let s = new Subject()
let o1 = new Observer('o1', s)
let o2 = new Observer('02', s)

s.setState(12)

实例化的时候把自己注册到订阅中心(主题)。订阅中心发生变动时,通知所有观察者更新自身。

场景:

  • DOM 事件
1
2
3
4
document.body.addEventListener('click', function () {
console.log('hello world!')
})
document.body.click()
  • Vue 响应式
  • IntersectionObserver 监听元素状态

组合模式

  • 将对象组合成树形结构,以表示“整体-部分”的层次结构。
  • 通过对象的多态表现,使得用户对单个对象和组合对象的使用具有一致性。
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
class TrainOrder {
create() {
console.log('创建火车票订单')
}
}
class HotelOrder {
create() {
console.log('创建酒店订单')
}
}

class TotalOrder {
constructor() {
this.orderList = []
}
addOrder(order) {
this.orderList.push(order)
return this
}
create() {
this.orderList.forEach((item) => {
item.create()
})
return this
}
}
// 可以在购票网站买车票同时也订房间
let train = new TrainOrder()
let hotel = new HotelOrder()
let total = new TotalOrder()
total.addOrder(train).addOrder(hotel).create()

享元模式

  • 大大减少对象的创建,降低系统的内存,使效率提高。

职责链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 请假审批,需要组长审批、经理审批、总监审批
class Action {
constructor(name) {
this.name = name
this.nextAction = null
}
setNextAction(action) {
this.nextAction = action
}
handle() {
console.log(`${this.name} 审批`)
if (this.nextAction != null) {
this.nextAction.handle()
}
}
}

let a1 = new Action('组长')
let a2 = new Action('经理')
let a3 = new Action('总监')
a1.setNextAction(a2)
a2.setNextAction(a3)
a1.handle()

场景:

  • JS 中的事件冒泡
  • 作用域链
  • 原型链

中介者模式

  • 解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的 相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知 中介者对象即可。
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
class A {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setB()
}
}
}
class B {
constructor() {
this.number = 0
}
setNumber(num, m) {
this.number = num
if (m) {
m.setA()
}
}
}
class Mediator {
constructor(a, b) {
this.a = a
this.b = b
}
setA() {
let number = this.b.number
this.a.setNumber(number * 10)
}
setB() {
let number = this.a.number
this.b.setNumber(number / 10)
}
}

let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(10, m)
console.log(a.number, b.number)
b.setNumber(10, m)
console.log(a.number, b.number)

装饰者模式

  • 动态地给函数赋能。动态地给某个对象添加一些额外的职责,是一种实现继承的替代方案。

  • 在不改变原对象的基础上,通过对其进行包装扩展,使原有对象可以满足用户的更复杂需求,而不会影响从这个类中派生的其他对象。

  • Vue 高阶组件

适配者模式

  • 将一个类的接口转化为另外一个接口,以满足用户需求,使类之间接口不兼容问题通过适配器得以解决。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Plug {
getName() {
return 'iphone充电头'
}
}

class Target {
constructor() {
this.plug = new Plug()
}
getName() {
return this.plug.getName() + ' 适配器Type-c充电头'
}
}

let target = new Target()
target.getName() // iphone充电头 适配器转Type-c充电头

场景:

  • 整合第三方 SDK
  • 封装旧接口