JavaScript-设计模式

2020/10/25 JavaScript
// 面向对象三要素
1. 继承:
    子类继承父类
    继承可将公共方法抽离出来,提高复用,减少冗余
2. 封装: // pulic、protected、private
    减少耦合,不该外露的不外露
    利于数据、接口的权限管理
    ES6目前不支持,一般认为_开头的属性时private
3. 多态:
    保持子类的开放性和灵活性
    面向接口编程,JS引用极少,相当于方法重写
----------------------------------------------------------------------------------------------
// 什么是设计
1. 即按照哪一种思路或者标准来实现功能
2. 功能相同,可以有不同设计方案来实现
3. 伴随着需求增加,设计的作用才能体现出来
----------------------------------------------------------------------------------------------
// 设计准则
1. 小即是美
2. 让每个程序只做好一件事
3. 快速建立原型进而自我更新
4. 舍弃高效率而取可移植性
5. 采用纯文本来存储数据非二进制
6. 充分利用软件的杠杆原理即复用
7. 使用shell脚本来提高杠杆效应和可移植性
8. 避免强制性的用户界面
9. 让每个程序都称为过滤器
----------------------------------------------------------------------------------------------
// 设计小准则
1. 允许用户定制环境
2. 尽量是操作系统内核小而轻量化
3. 使用小写字母并尽量简短
4. 沉默是金
5. 各部分之和大于整体
6. 寻求90%的解决方案
----------------------------------------------------------------------------------------------
// SOLID五大设计原则
1. S —— 单一职责原则
    一个程序只做好一件事
    如果功能过于复杂就拆分开,每个部分保持独立
2. O —— 开放封闭原则
    对扩展开放,对修改封闭
    增加需求时,扩展新代码,而非修改已有代码
    这是软件设计的终极目标
3. L —— 李氏置换原则
    子类能覆盖父类
    父类能出现在的地方子类就能出现
    JS中使用较少 // 弱类型语言
4. I —— 接口独立原则
    保持接口的单一独立,避免出现胖接口
    JS中没有接口 // ts有
    类似于单一职责原则,这里更关注接口
5. D —— 依赖导致原则
    面向接口编程,依赖于抽象而不依赖于具体
    使用方只关注接口而不关注具体类的实现
    JS中使用较少 // 没有接口,弱类型语言
----------------------------------------------------------------------------------------------
// 设计模式简介
1. 创建型
    工厂模式,包括工厂方法模式、抽象工厂模式、建造者模式 // 常用
    单例模式 // 常用
    原型模式
2. 结构型
    适配器模式 // 常用
    装饰器模式 // 常用
    代理模式 // 常用
    外观模式 // 常用
    桥接模式
    组合模式
    享元模式
3. 行为型
    策略模式
    模板方法模式
    观察者模式 // 常用
    迭代器模式 // 常用
    职责连模式
    命令模式
    备忘录模式
    状态模式 // 常用
    访问者模式
    中介者模式
    解释器模式
----------------------------------------------------------------------------------------------
// 工厂模式
1.new操作单独封装
2. 遇到new时就要考虑是否该使用工厂模式
3. 使用场景:
    jQuery的 $('div')
    React.createElement
    vue异步组件
4. 示例:
class Product {
  constructor(name) {
    this.name = name
  }
  init() {
    console.log('init')
  }
  run() {
    console.log('run')
  }
}
class Creator { // 工厂
  create(name) {
    return new Product(name)
  }
}
const creator = new Creator()
const p = creator.create('p1')
p.init()
p.run()
----------------------------------------------------------------------------------------------
// 单例模式
1. 系统中被唯一使用
2. 一个类只有一个实例
3. 单例模式需要用到 private 特性 // ts
4. 使用场景:
    jQuery只有一个 $ ,不管引入多少个
    if (window.jQuery != null) {
      return window.jQuery
    } else {
      // 初始化
    }
5. 示例:
class SingleObject {
  login() {
    console.log('login')
  }
}
SingleObject.getInstance = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new SingleObject()
    }
    return instance
  }
})()
const obj1 = SingleObject.getInstance()
obj1.login()
const obj2 = SingleObject.getInstance()
obj2.login()
console.log(obj1 === obj2) // true
----------------------------------------------------------------------------------------------
// 适配器模式
1. 旧接口格式和使用者不兼容
2. 中间加一个适配转换接口
3. 使用场景:
    封装旧接口
    vue computed
4. 示例:
class Adaptee {
  specificRequest() {
    return '德国标准的插头'
  }
}
class Target {
  constructor() {
    this.adaptee = new Adaptee()
  }
  request() {
    const info = this.adaptee.specificRequest()
    return `${info} -> 转换器 -> 中国标准的插头`
  }
}
const target = new Target()
target.request()
----------------------------------------------------------------------------------------------
// 装饰器模式
1. 为对象添加新功能
2. 不改变其原有的结构和功能
3. 示例1class Circle {
  draw() {
    console.log('画一个圆形)
  }
}
class Decorator {
  constructor(circle) {
    this.circle = circle
  }
  draw() {
    this.circle.draw()
    this.setRedBorder(circle)
  }
  setRedBorder(circle) {
    console.log('设置红色边框')
  }
}
const circle = new Circle()
circle.draw()

const dec = new Decorator(circle)
dec.draw()
3. 示例2:装饰类1
// 相当于把类传到函数中去执行
// class Demo {} Demo = testDec(Demo) || Demo
function testDec(target) {
  target.isDec = true
}

@testDec
class Demo {
  init() {
    console.log('init')
  }
}

console.log(Demo.isDec) // true
3. 示例3:装饰类2
function mixins(...list) {
  return function(target) {
    Object.assign(target.prototype, ...list)
  }
}
const Foo = {
  foo() {
    console.log('foo')
  }
}

@mixins(Foo)
class Demo {}
const obj = new Demo()
obj.foo() // foo
3. 示例4:装饰方法
function log(target, name, descriptor) {
  // target 当前的类
  // name 属性名
  // descriptor 属性描述符
  let oldValue = descriptor.value
  descriptor.value = function() {
    console.log(`calling ${name} width`, arguments)
    return oldValue.apply(this, arguments)
  }
  return descriptor
}

class Math {
  @log
  add(a, b) {
    return a + b
  }
}

const math = new Math()
console.log(math.add(2, 4))
4. 可以使用 npm install core-decorators 这个库,提供了常用的装饰器
----------------------------------------------------------------------------------------------
// 代理模式,类似科学上网或明星经纪人
1. 使用者无权访问目标对象
2. 中间加代理,通过代理做授权和控制
3. 使用场景:
    网页事件代理
    jQuery.$proxy
    ES6 proxy
4. 示例:
class ReadImg {
  constructor(fileName) {
    this.fileName = fileName
    this.loadFromDisk()
    display() {
      console.log('display')
    }
    loadForomDisk() {
      console.log('loadForomDisk')
    }
  }
}
class ProxyImg {
  constructor(fileName) {
    this.realImg = new ReadImg(fileName)
  }
  display() {
    this.realImg.display()
  }
}

const proxyImg = new ProxyImg('1.png')
proxyImg.display()
----------------------------------------------------------------------------------------------
// 适配器模式、装饰器模式和代理模式的区别
1. 适配器模式 vs 代理模式
   适配器模式:提供一个不同的接口 // 如不同版本的插头
   代理模式:提供一模一样的接口
2. 装饰器模式 vs 代理模式
   装饰器模式:扩展功能,原有功能不变且可直接使用
   代理模式:显示原有功能,但是经过限制或者阉割之后的
----------------------------------------------------------------------------------------------
// 外观模式
1. 为子系统中的一组接口提供了一个高层接口
2. 使用者使用这个高层接口
----------------------------------------------------------------------------------------------
// 观察者模式
1. 发布
2. 订阅
3. 一对N // 也可能会一对一
4. 使用场景:
    网页事件绑定
    Promise
    jQuery callbacks
    node自定义事件
    node处理http请求,多进程通讯
    vue和react组件生命周期触发
    vue watch
5. 示例:
class subject {
  constructor() {
    this.state = 0
    this.observers = []
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
    this.notifyAllObservers()
  }
  notifyAllObservers() {
    this.observers.forEach(observer => {
      observers.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()}`)
  }
}

const s = new Subject()
const o1 = new Observer('o1', s)
const o2 = new Observer('o2', s)
const o3 = new Observer('o3', s)

s.setState(1)
s.setState(2)
s.setState(3)
----------------------------------------------------------------------------------------------
// 迭代器模式
1. 顺序访问一个集合 // 数组
2. 使用者无需知道集合的内部结构 // 封装
3. 使用场景:
    jQuery each
    ES6 Iterator
4. 示例1class Iterator {
  constructor(container) {
    this.list = container.list
    this.index = 0
  }
  next() {
    if (this.hasNext()) {
      return this.list[this.index++]
    }
    return null
  }
  hasNext() {
    if (this.index >= this.list.length) {
      return false
    }
    return true
  }
}

class Container {
  constructor(list) {
    this.list = list
  }
  // 生成遍历器
  getIterator() {
    return new Iterator(this)
  }
}

const container = new Container([1, 2, 3, 4, 5])
const iterator = container.getIterator()
while(iterator.hasNext()) {
  console.log(iterator.next())
}

4. 示例2// 如何能写出一个方法来遍历这三种对象?
function each(data) {
  var $data = $(data) // 生成迭代器
  $data.each(function(key, p) {
    console.log(key, p)
  })
}

each(arr) // arr可以用forEach
each(nodeList) // nodeList是伪数组没有forEach
each($p) // $p是jQuery的获取的元素

4. 示例3// ES6 Iterator 为何存在?
4.1ES6语法中,有序集合的数据类型有 Array、Map、Set、String、arguments、nodeList、TypedArray
4.2:需要有一个统一的遍历接口来遍历所有的数据类型即 'for...of' ,注意 Object 不是有序集合,可以用 Map 代替
// ES6 Iterator 是什么?
4.3:以上数据类型,都有 [Symbol.iterator] 属性
4.4:属性值是函数,执行函数返回一个迭代器
4.5:这个迭代器就有 next() 方法可顺序迭代子元素
4.6:可运行 Array.prototype[Symbol.iterator] 来测试
// ES6 Iterator和 Generator
4.7:Iterator 的价值不限于上述几个类型的遍历,还有 Generator 的使用
4.8:即只要返回的数据符合 Iterator 接口的要求即可使用 Iterator 语法,这就是迭代器模式
----------------------------------------------------------------------------------------------
// 状态模式
1. 一个对象有状态变化 // 类似红绿灯
2. 每次状态变化都会触发一个逻辑,不能总是用 if else 来控制
3. 示例:
class State {
  constructor(color) {
    this.color = color
  }
  handle(context) {
    console.log(`turn to ${this.color} light`)
    context.setState(this)
  }
}

class Context {
  constructor() {
    this.state = null
  }
  getState() {
    return this.state
  }
  setState(state) {
    this.state = state
  }
}

const context = new Context()
const green = new State('green')
const yellow = new State('yellow')
const red = new State('red')

green.handle(context)
console.log(context.getState())

yellow.handle(context)
console.log(context.getState())

red.handle(context)
console.log(context.getState())
----------------------------------------------------------------------------------------------
// 原型模式
1. 概念:
    clone 自己,生成一个新对象
    java默认有 clone 接口,不用自己实现
2. 示例:
    Object.create 用到了原型模式的思想
    // 一个原型对象
    const prototype = {
      getName: function() {
        return this.first + ' ' + this.last
      },
      say: function () {
        alert('hello')
      }
    }
    // 基于原型创建 x
    let x = Object.create(prototype)
    x.first = 'A'
    x.last = 'B'
    alert(x.getName())
    x.say()
    // 基于原型创建 y
    let x = Object.create(prototype)
    y.first = 'C'
    y.last = 'D'
    alert(x.getName())
    y.say()
3. 对比 js 中的原型 prototype
    prototype 可以理解为 ES6 'class' 的一种底层原理,而 'class' 是实现面向对象的基础,并不是服务于某个模式
    若干年后 ES6 全面普及,大家可能会忽略掉 prototype 但是 Object.creact 却会长久存在
----------------------------------------------------------------------------------------------
// 桥接模式
1. 概念:
    用于把抽象化与实现化解耦
    使得二者可以独立变化
2. 示例:
    比如要实现一个黄色的三角形可以抽象成画出三角形和上色,组合起来就是黄色的三角形
----------------------------------------------------------------------------------------------
// 组合模式
1. 概念:
    生成树形结构,表示'整体-部分'关系
    让整体和部分都具有一致的操作方式
2. 示例:
    虚拟 DOM 中的 VNode
----------------------------------------------------------------------------------------------
// 享元模式
1. 概念:
    共享内存 // 主要考虑内存,而非效率
    相同的数据,共享使用
2. 示例:
    document 事件代理
----------------------------------------------------------------------------------------------
// 策略模式
1. 概念:
    不同策略分开处理
    避免出现大量 if else 或 swich case
2. 示例:
class User {
  constructor(type) {
    this.type = type
  }
  buy() {
    if (this.type === 'ordinary') {
      console.log('普通用户购买')
    } else if (this.type === 'member') {
      console.log('会员用户购买')
    } else if (this.type === 'vip') {
      console.log('vip用户购买')
    }
  }
}

// 上面代码可以变成下面

class Ordinary {
  buy() {
    console.log('普通用户购买')
  }
}
class Member {
  buy() {
    console.log('会员用户购买')
  }
}
class Vip {
  buy() {
    console.log('vip用户购买')
  }
}
----------------------------------------------------------------------------------------------
// 模板方法模式
1. 概念:
    模块拆分、解耦合
2. 示例:
class Action {
  handle() {
    handle1()
    handle2()
    handle3()
  }
  handle1() {
    console.log(1)
  }
  handle2() {
    console.log(2)
  }
  handle3() {
    console.log(3)
  }
}
----------------------------------------------------------------------------------------------
// 职责链模式
1. 概念:
    一步操作可能分为多个职责角色来完成
    把这些角色都分开,然后用一个链串起来
    将发起者和各个处理者进行隔离
2. 示例:
// 请假审批,需要组长审批、经理审批、最后总监审批
class Action {
  constructor(name) {
    this.name = name
    this.nextAction = null
  }
  setNextAction(action) {
    this.nextAction = action
  }
  handle() {
    consoel.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()
----------------------------------------------------------------------------------------------
// 命令模式
1. 概念:
    发送者 ——> 命令对象 ——> 接收者
2. 示例:
// 接收者
class Receiver {
  exec() {
    console.log('执行')
  }
}
// 命令者
class Command {
  constructor(receiver) {
    this.receiver = receiver
  }
  cmd() {
    console.log('执行命令')
    this.receiver.exec()
  }
}
// 触发者
class Invoker {
  constructor(command) {
    this.command = command
  }
  invoke() {
    console.log('开始')
    this.command.cmd()
  }
}

// 士兵
let soldier = new Receiver()
// 小号手
let trumpeter = new Command(soldier)
// 将军
let general = new Invoker(trumpeter)
general.invoke()
----------------------------------------------------------------------------------------------
// 备忘录模式
1. 概念:
    随时记录一个对象的状态变化
    随时可以恢复之前的某个状态 // 如撤销功能
2. 示例:
// 备忘类
class Memento {
  constructor(content) {
    this.content = content
  }
  getContent() {
    return this.content
  }
}
// 备忘列表
class CareTaker {
  constructor() {
    this.list = []
  }
  add(memento) {
    this.list.push(memento)
  }
  get(index) {
    return this.list[index]
  }
}
// 编辑器
class Editor {
  constructor() {
    this.content = null
  }
  setContent(content) {
    this.content = content
  }
  getContent() {
    return this.content
  }
  saveContentToMemento() {
    return new Memento(this.content)
  }
  getContentFromMemento(memento) {
    this.content = memento.getContent()
  }
}

let editor = new Editor()
let careTaker = new CareTaker()
editor.setContent(1)
editor.setContent(2)
careTaker.add(editor.saveContentToMemento()) // 将当前内容备份
editor.setContent(3)
careTaker.add(editor.saveContentToMemento()) // 将当前内容备份
editor.setContent(4)

console.log(editor.getContent())
editor.getContentFromMemento(careTaker.get(1)) // 撤销
console.log(editor.getContent())
editor.getContentFromMemento(careTaker.get(0)) // 撤销
console.log(editor.getContent())
----------------------------------------------------------------------------------------------
// 中介者模式
1. 概念:
    中介者 // 中间人
2. 示例:
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
  }
  setB() {
    let number = this.a.number
    this.b.setNumber(number * 100)
  }
  setA() {
    let number = this.b.number
    this.a.setNumber(number / 100)
  }
}

let a = new A()
let b = new B()
let m = new Mediator(a, b)
a.setNumber(100, m)
console.log(a.number, b.number)
b.setNumber(100, m)
console.log(a.number, b.number)
----------------------------------------------------------------------------------------------
// 访问者模式
1. 概念:
    将数据操作和数据结构进行分离
----------------------------------------------------------------------------------------------
// 解释器模式
1. 概念:
    描述语言语法如何定义,如何解释和编译
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
Last Updated: 2024/6/11 14:20:38
    飘向北方
    那吾克热-NW,尤长靖