设计模式应用
# 单例模式
var proxySingleton = (function() {
var singer = null;
return function(Func) {
if (!singer) {
singer = new Func();
}
return singer;
};
})();
class Singleton {
constructor(name) {
this.name = name;
this.instance = null;
}
static getInstance(name) {
if (!this.instance) {
this.instance = new Singleton(name);
}
return this.instance;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 发布订阅模式
export enum EventName {
HandleEvent = "HandleEvent",
}
export interface EventCallbackMap {
[EventName.HandleEvent]: [];
}
export class EventEmitter<T extends EventName = any> {
eventsStore: Map<any, any>;
constructor() {
this.eventsStore = new Map(); // 储存{事件:回调}键值对
}
emit(type: T, ...args: EventCallbackMap[T]) {
console.log("emit ", type);
//从存储的Map键值对this._events中获取对应事件回调函数
const handler = this.eventsStore.get(type);
if (!handler) {
return false;
}
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
if (args.length > 0) {
handler[i].apply(this, args);
} else {
handler[i].call(this);
}
}
} else {
if (args.length > 0) {
handler.apply(this, args);
} else {
handler.call(this);
}
}
return true;
}
on(type: T, fn: (...args: EventCallbackMap[T]) => void) {
console.log("on ", type);
const handler = this.eventsStore.get(type);
if (!handler) {
this.eventsStore.set(type, fn);
} else if (handler && typeof handler === "function") {
//hanlder为函数,说明只有一个监听者
//多个监听者用数组存储
this.eventsStore.set(type, [handler, fn]);
} else {
//已有多个监听者,直接Push进数组
handler.push(fn);
}
}
delete(type: T, fn: (...args: EventCallbackMap[T]) => void) {
const handler = this.eventsStore.get(type);
// 如果是函数,说明只被监听了一次
if (handler && typeof handler === "function") {
this.eventsStore.delete(type);
} else {
let postion;
for (let i = 0; i < handler.length; i++) {
if (handler[i] === fn) {
postion = i;
} else {
postion = -1;
}
}
if (postion !== -1) {
handler.splice(postion, 1);
if (handler.length === 1) {
this.eventsStore.set(type, handler[0]);
}
} else {
return this;
}
}
}
}
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
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
# 配合发布订阅模式的商品抢购计时器
enum CountdownStatus {
opening,
stoped,
paused
}
interface CountdownOptions {
showTwoMillSecond?: boolean;
}
export class Countdown extends EventEmitter {
private static COUNT_IN_MILLISECOND: number = 1 * 100;
private static SECOND_IN_MILLISECOND: number =
10 * Countdown.COUNT_IN_MILLISECOND;
private static MINUTE_IN_MILLISECOND: number =
60 * Countdown.SECOND_IN_MILLISECOND;
private static HOUR_IN_MILLISECOND: number =
60 * Countdown.MINUTE_IN_MILLISECOND;
private static DAY_IN_MILLISECOND: number =
24 * Countdown.HOUR_IN_MILLISECOND;
private endTime: number;
private step: number;
private options: CountdownOptions;
private remainTime: number = 0;
private status: CountdownStatus = CountdownStatus.stoped;
constructor(
endTime: number,
step: number = 1e3, // 程序中"1e3" = 1 x 10 ^ 3
options: CountdownOptions = {}
) {
super();
this.endTime = endTime;
this.step = step;
this.options = options;
}
countdown() {
if (this.status !== CountdownStatus.opening) return;
// 相差时间值 如果给到的时间本身就小于当前时间,则等于0后不执行
this.remainTime = Math.max(this.endTime - Date.now(), 0);
// 每一次执行计时器发生计时触发发布订阅模式中用户添加的方法,返回时间参数
this.emit("countdown", this.parseRemainTime(this.remainTime));
if (this.remainTime > 0) {
setTimeout(() => this.countdown(), this.step);
} else {
this.stop();
}
}
stop() {
this.emit("stop");
this.status = CountdownStatus.stoped;
}
opening() {
this.emit("opening");
this.status = CountdownStatus.opening;
}
pause() {
this.emit("pause");
this.status = CountdownStatus.paused;
}
private parseRemainTime(remainTime: number) {
let time = remainTime;
const days = Math.floor(time / Countdown.DAY_IN_MILLISECOND);
time = time % Countdown.DAY_IN_MILLISECOND;
const hours = Math.floor(time / Countdown.HOUR_IN_MILLISECOND);
time = time % Countdown.HOUR_IN_MILLISECOND;
const minutes = Math.floor(time / Countdown.MINUTE_IN_MILLISECOND);
time = time % Countdown.MINUTE_IN_MILLISECOND;
const seconds = Math.floor(time / Countdown.SECOND_IN_MILLISECOND);
time = time % Countdown.SECOND_IN_MILLISECOND;
let count = time;
if (!this.options.showTwoMillSecond) {
count = Math.floor(time / Countdown.COUNT_IN_MILLISECOND);
}
return {
days,
hours,
minutes,
seconds,
count
};
}
}
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
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
# 观察者模式和订阅-发布模式的区别,各自适用于什么场景
观察者模式中的主题是相互感知的,发布订阅模式中的主题是相互不感知的,是借助第三方来发布调度的。
# 设计模式六大原则
# OCP 原则
OCP Open Closed Principle (开闭原则)。软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
如何遵循抽象约束:
a) 通过接口或抽象类约束扩展,对扩展进行边界限定,不允许出现在接口或抽象类中的不存在的 public 方法;
b) 参数类型、引用对象尽量使用接口或者抽象类,而不是实现类;
c) 抽象层尽量保持稳定,一旦确定即不允许修改。
封装变化:
a) 将相同的变化封装到一个接口或抽象类中;
b) 将不同的变化封装到不同的接口或抽象类中,不应该有两个不同的变化出现在同一接口或抽象类中。(23 设计模式也是从各个不同的角度对变化进行封装的)
最佳实践: 封装变化:按可能变化的不同去封装变化; 抽象约束:抽象层尽量保持稳定,一旦确定即不允许修改。
例如,我去买书,买海绵宝宝的会提示货物不足, 买派大星的会弹出支付,如果购买的这个物品是极品(JP),则会返回高亮的效果
if (buy === "hmbb") {
alert("货物不足");
} else {
// normal
}
if (buy === "pdx") {
payfor("10000");
} else {
// normal
}
// 如果有极品
if (buy === "hmbb") {
alert("货物不足");
} else if (buy === "jp") {
// 高亮
} else {
// normal
}
//OCP原则重构
class Mall {
constructor(buy) {
this.buy = buy;
}
setHighLight() {
// 正常
}
openDialog() {
// 正常
}
}
class HMBB extends Mall {
openDialog() {
// 货物不足弹窗
}
}
class PDX extends Mall {
openDialog() {
// payfor弹窗
}
}
class JP extends Mall {
setHighLight() {
// 高亮
}
}
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
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
# SRP 原则
SRP -单一职责原则
- 目标: 一个模块只能做一件事, 通过解耦让模块更加独立
// 需求: game store 设计开发结算模块
class PUBGManager {
// 弹出结算框
openDialog() {
// 计算金额
setPrice();
}
}
const game = new PUBGManager();
game.openDialog(); // 弹窗 < = > setPrice 耦合
// 重构
// 拆分计算金额和结算弹框
// gameManager.js
class PUBGManager {
constructor(command) {
this.command = command;
}
openDialog(price) {
// 计算金额
this.command.setPrice(price);
}
}
class PriceManager {
setPrice(price) {
// 计算金额
}
}
const exe = new PriceManager();
const game = new PUBGManager(exe);
game.openDialog(15);
exe.setPrice(10);
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
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
编辑 (opens new window)
上次更新: 2023/03/22, 15:28:00