参考

JavaScript 中常见的十五种设计模式(上)
JavaScript 中常见的十五种设计模式(下)

单例模式

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点

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
// 实现方式
// 比如可以使用闭包缓存一个内部变量来实现这个单例
function SetManager(name) {
this.manager = name;
}

SetManager.prototype.getName = function () {
console.log(this.manager);
};
// 后续写法 1
// let SingletonSetManager = (function() {
// let manager = null;
// // 内部变量保存类的实例,存在则继续沿用这个实例
// return function(name) { // 传参给闭包函数 SingletonSetManager 的 name,其实是传给其 return 的函数
// if (!manager) {
// manager = new SetManager(name);
// }
// return manager;
// }
// })();
// SingletonSetManager('a').getName(); // a
// SingletonSetManager('b').getName(); // a
// SingletonSetManager('c').getName(); // a

// 后续写法 2
// 提取出通用的单例
function getSingleton(fn) {
let instance = null;

return function () {
if (!instance) {
instance = fn.apply(this, arguments); // 等于 this.fn(arguments)
console.log("arguments: " + JSON.stringify(arguments)); // arguments 直接可打印出来
}

return instance;
};
}

// 获取单例
let managerSingleton = getSingleton(function (name) {
let manager = new SetManager(name);
return manager;
});

managerSingleton("a").getName(); // a
managerSingleton("b").getName(); // a
managerSingleton("c").getName(); // a

策略模式

定义

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换

分离

使用 ← 分离 → 实现

组成

  • 一组策略类,封装具体算法,负责具体计算

  • 环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类

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
// eg:表单的验证方法

// 错误提示
let errorMsgs = {
default: "输入数据格式不正确",
minLength: "输入数据长度不足",
isNumber: "请输入数字",
required: "内容不为空",
};

// 规则集
let rules = {
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg || errorMsgs["minLength"];
}
},
isNumber: function (value, errorMsg) {
if (!/\d+/.test(value)) {
return errorMsg || errorMsgs["isNumber"];
}
},
required: function (value, errorMsg) {
if (value === "") {
return errorMsg || errorMsgs["required"];
}
},
};

// 校验器
function Validator() {
this.items = [];
}

Validator.prototype = {
constructor: Validator,

// 添加校验规则
add: function (value, rule, errorMsg) {
let arg = [value];

if (rule.indexOf("minLength") !== -1) {
let temp = rule.split(":");
arg.push(temp[1]);
rule = temp[0];
}

arg.push(errorMsg);

this.items.push(function () {
// 进行校验
return rules[rule].apply(this, arg);
});
},

// 开始校验
start: function () {
for (let i = 0; i < this.items.length; ++i) {
let ret = this.items[i]();

if (ret) {
console.log(ret);
// return ret;
}
}
},
};

// 测试数据
function testTel(val) {
return val;
}

let validate = new Validator();

validate.add(testTel("ccc"), "isNumber", "只能为数字"); // 只能为数字
validate.add(testTel(""), "required"); // 内容不为空
validate.add(testTel("123"), "minLength:5", "最少5位"); // 最少5位
validate.add(testTel("12345"), "minLength:5", "最少5位");

let ret = validate.start();

console.log(ret);

代理模式

保护代理

虚拟代理

在控制对主体的访问时,加入了一些额外的操作

缓存代理

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
// 虚拟代理
/**
* 函数节流:
* mustRunDelay 时间内重复执行【延迟 delay 时间执行 fn】,到达 mustRunDelay 时间长度则马上执行 fn
* eg: window.onscroll = throttle(fn, delay, mustRunDelay)
* @param fn 函数
* @param delay 多久执行一次
* @param mustRunDelay 执行时间间隔
* @return {Function}
*/
throttle(fn, delay, mustRunDelay) {
/*eslint-disable*/
let timer = null
let t_start
return function () {
let context = this, args = arguments, t_curr = +new Date()
// 清除定时操作
clearTimeout(timer)
if (!t_start) {
t_start = t_curr
}
// 时间间隔超过 mustRunDelay 则马上执行 fn
if (t_curr - t_start >= mustRunDelay) {
fn.apply(context, args)
t_start = t_curr
}
else {
// 定义新的定时器,一段时间后进行操作
timer = setTimeout(function () {
fn.apply(context, args)
}, delay)
}
}
}


// 缓存代理
// 缓存加法操作

// 主体
function add () {
let arg = [].slice.call(arguments);
return arg.reduce((a, b) => { a + b });
}

// 代理
let proxyAdd = (function () {
let cache = [];

return function () {
let arg = [].slice.call(arguments).join(',');

// 如果有,则直接从缓存返回
if (cache[arg]) {
return cache[arg];
} else {
let ret = add.apply(this, arguments);
return ret;
}
};
})();

console.log(
add(1, 2, 3, 4),
add(1, 2, 3, 4),

proxyAdd(10, 20, 30, 40),
proxyAdd(10, 20, 30, 40)
); // 10 10 100 100

迭代器模式

迭代器模式是指提供一种方法 顺序访问 一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示

在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素

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
// 实现 ES6 的 迭代器
function createIterator(items) {
let i = 0;
return {
next: function () {
let done = i >= items.length;
let value = !done ? items[i++] : undefined;
return {
done: done,
value: value,
};
},
};
}
let iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"
// 之后的所有调用
console.log(iterator.next()); // "{ value: undefined, done: true }"

// 迭代器 eg
function year2000() {
let year = new Date().getFullYear();
if (year <= 2000) {
console.log("A");
}
return false;
}

function year2100() {
let year = new Date().getFullYear();
if (year >= 2100) {
console.log("C");
}
return false;
}

function year() {
let year = new Date().getFullYear();
if (year > 2000 && year < 2100) {
console.log("B");
}
return false;
}

function iteratorYear() {
for (let i = 0; i < arguments.length; ++i) {
let ret = arguments[i]();
if (ret !== false) {
return ret;
}
}
}

let manager = iteratorYear(year2000, year2100, year); // B

发布-订阅模式

也称作观察者模式

eg

小 A 在公司 C 完成了笔试及面试,小 B 也在公司 C 完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司 C,导致公司 C 很不耐烦。

一种解决办法是 AB 直接把联系方式留给 C,有结果的话 C 自然会通知 AB

这里的询问属于显示 调用,留给属于 订阅,通知属于 发布

优缺点

一为时间上的解耦,二为对象之间的解耦

  • 创建订阅者本身要消耗一定的时间和内存,订阅的处理函数不一定会被执行,驻留内存有性能开销

  • 弱化了对象之间的联系,复杂的情况下可能会导致程序难以跟踪维护和理解

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
// 观察者
let observer = {
// 订阅集合
subscribes: [],
// 订阅
subscribe: function (type, fn) {
if (!this.subscribes[type]) {
this.subscribes[type] = [];
}
// 收集订阅者针对订阅内容的处理函数
typeof fn === "function" && this.subscribes[type].push(fn);
// log 为 bool, 但这种表达式有啥意义?
},

// 发布 可能会携带一些信息发布出去
publish: function () {
let type = [].shift.call(arguments),
fns = this.subscribes[type];
console.log("发布 ", fns);
// 不存在的订阅类型,以及订阅时未传入处理回调的
if (!fns || !fns.length) {
return;
}
// 挨个处理调用
for (let i = 0; i < fns.length; ++i) {
fns[i].apply(this, arguments);
}
},

// 删除订阅
remove: function (type, fn) {
// 删除全部
if (typeof type === "undefined") {
this.subscribes = [];
return;
}
let fns = this.subscribes[type];
// 不存在的订阅类型,以及订阅时未传入处理回调的
if (!fns || !fns.length) {
return;
}
if (typeof fn === "undefined") {
fns.length = 0;
return;
}
// 挨个处理删除
for (let i = 0; i < fns.length; ++i) {
if (fns[i] === fn) {
fns.splice(i, 1);
}
}
},
};

// 订阅岗位列表
function jobListForA(jobs) {
console.log("A", jobs);
}

function jobListForB(jobs) {
console.log("B", jobs);
}

// A订阅了笔试成绩
observer.subscribe("job", jobListForA);
// B订阅了笔试成绩
observer.subscribe("job", jobListForB);

// A订阅了笔试成绩
observer.subscribe("examinationA", function (score) {
console.log(score);
});

// B订阅了笔试成绩
observer.subscribe("examinationB", function (score) {
console.log(score);
});

// A订阅了面试结果
observer.subscribe("interviewA", function (result) {
console.log(result);
});

observer.publish("examinationA", 100); // 100
observer.publish("examinationB", 80); // 80
observer.publish("interviewA", "备用"); // 备用

observer.publish("job", ["前端", "后端", "测试"]); // 输出A和B的岗位

// B取消订阅了笔试成绩
observer.remove("examinationB");
// A都取消订阅了岗位
observer.remove("job", jobListForA);

observer.publish("examinationB", 80); // 没有可匹配的订阅,无输出
observer.publish("job", ["前端", "后端", "测试"]); // 输出B的岗位

命令模式

实现

简单的命令模式实现可以直接使用 对象字面量 的形式定义一个命令

1
2
3
4
5
let incrementCommand = {
execute: function () {
// something
},
};

采用对象创建处理的方式

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
// 自增
function IncrementCommand() {
// 当前值
this.val = 0;
// 命令栈
this.stack = [];
// 栈指针位置
this.stackPosition = -1;
}

IncrementCommand.prototype = {
constructor: IncrementCommand,

// 执行
execute: function () {
this._clearRedo();

// 定义执行的处理
let command = function () {
this.val += 2;
}.bind(this);

// 执行并缓存起来
command();

this.stack.push(command);

this.stackPosition++;

this.getValue();
},

canUndo: function () {
return this.stackPosition >= 0;
},

canRedo: function () {
return this.stackPosition < this.stack.length - 1;
},

// 撤销
undo: function () {
if (!this.canUndo()) {
return;
}

this.stackPosition--;

// 命令的撤销,与执行的处理相反
let command = function () {
this.val -= 2;
}.bind(this);

// 撤销后不需要缓存
command();

this.getValue();
},

// 重做
redo: function () {
if (!this.canRedo()) {
return;
}

// 执行栈顶的命令
this.stack[++this.stackPosition]();

this.getValue();
},

// 在执行时,已经撤销的部分不能再重做
_clearRedo: function () {
this.stack = this.stack.slice(0, this.stackPosition + 1);
},

// 获取当前值
getValue: function () {
console.log(this.val);
},
};

组合模式

使用组合模式来实现扫描文件夹中的文件

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
// 文件夹 组合对象
function Folder(name) {
this.name = name;
this.parent = null;
this.files = [];
}

Folder.prototype = {
constructor: Folder,

add: function (file) {
file.parent = this;
this.files.push(file);

return this;
},

scan: function () {
// 委托给叶对象处理
for (let i = 0; i < this.files.length; ++i) {
this.files[i].scan();
}
},

remove: function (file) {
if (typeof file === "undefined") {
this.files = [];
return;
}

for (let i = 0; i < this.files.length; ++i) {
if (this.files[i] === file) {
this.files.splice(i, 1);
}
}
},
};

// 文件 叶对象
function File(name) {
this.name = name;
this.parent = null;
}

File.prototype = {
constructor: File,

add: function () {
console.log("文件里面不能添加文件");
},

scan: function () {
let name = [this.name];
let parent = this.parent;

while (parent) {
name.unshift(parent.name);
parent = parent.parent;
}

console.log(name.join(" / "));
},
};

实例化,在组合对象中插入组合或叶对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
let web = new Folder("Web");
let fe = new Folder("前端");
let css = new Folder("CSS");
let js = new Folder("js");
let rd = new Folder("后端");

web.add(fe).add(rd);

let file1 = new File("HTML权威指南.pdf");
let file2 = new File("CSS权威指南.pdf");
let file3 = new File("JavaScript权威指南.pdf");
let file4 = new File("MySQL基础.pdf");
let file5 = new File("Web安全.pdf");
let file6 = new File("Linux菜鸟.pdf");

css.add(file2);
fe.add(file1).add(file3).add(css).add(js);
rd.add(file4).add(file5);
web.add(file6);

rd.remove(file4);

// 扫描
web.scan();

模板方法模式

模板方法模式一般的实现方式为继承

以运动作为例子,运动有比较通用的一些处理,这部分可以抽离开来,在父类中实现。具体某项运动的特殊性则有自类来重写实现。

最终子类直接调用父类的模板函数来执行

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
// 体育运动
function Sport() {}

Sport.prototype = {
constructor: Sport,

// 模板,按顺序执行
init: function () {
this.stretch();
this.jog();
this.deepBreath();
this.start();

let free = this.end();

// 运动后还有空的话,就拉伸一下
if (free !== false) {
this.stretch();
}
},

// 拉伸
stretch: function () {
console.log("拉伸");
},

// 慢跑
jog: function () {
console.log("慢跑");
},

// 深呼吸
deepBreath: function () {
console.log("深呼吸");
},

// 开始运动
start: function () {
throw new Error("子类必须重写此方法");
},

// 结束运动
end: function () {
console.log("运动结束");
},
};

// 篮球
function Basketball() {}

Basketball.prototype = new Sport();

// 重写相关的方法
Basketball.prototype.start = function () {
console.log("先投上几个三分");
};

Basketball.prototype.end = function () {
console.log("运动结束了,有事先走一步");
return false;
};

// 马拉松
function Marathon() {}

Marathon.prototype = new Sport();

let basketball = new Basketball();
let marathon = new Marathon();

// 子类调用,最终会按照父类定义的顺序执行
basketball.init();
marathon.init();

享元模式

享元(flyweight)模式是一种用于性能优化的模式,它的目标是尽量减少共享对象的数量

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
// 健康测量
function Fitness(sex) {
this.sex = sex;
}

// 工厂,创建可共享的对象
let FitnessFactory = {
objs: [],

create: function (sex) {
if (!this.objs[sex]) {
this.objs[sex] = new Fitness(sex);
}

return this.objs[sex];
},
};

// 管理器,管理非共享的部分
let FitnessManager = {
fitnessData: {},

// 添加一项
add: function (name, sex, age, height, weight) {
let fitness = FitnessFactory.create(sex);

// 存储变化的数据
this.fitnessData[name] = {
age: age,
height: height,
weight: weight,
};

return fitness;
},

// 从存储的数据中获取,更新至当前正在使用的对象
updateFitnessData: function (name, obj) {
let fitnessData = this.fitnessData[name];

for (let item in fitnessData) {
if (fitnessData.hasOwnProperty(item)) {
obj[item] = fitnessData[item];
}
}
},
};

// 开始评判
Fitness.prototype.judge = function (name) {
// 操作前先更新当前状态(从外部状态管理器中获取)
FitnessManager.updateFitnessData(name, this);

let ret = name + ": ";

if (this.sex === "male") {
ret += this.judgeMale();
} else {
ret += this.judgeFemale();
}

console.log(ret);
};

// 男性评判规则
Fitness.prototype.judgeMale = function () {
let ratio = this.height / this.weight;

return this.age > 20 ? ratio > 3.5 : ratio > 2.8;
};

// 女性评判规则
Fitness.prototype.judgeFemale = function () {
let ratio = this.height / this.weight;

return this.age > 20 ? ratio > 4 : ratio > 3;
};

let a = FitnessManager.add("A", "male", 18, 160, 80);
let b = FitnessManager.add("B", "male", 21, 180, 70);
let c = FitnessManager.add("C", "female", 28, 160, 80);
let d = FitnessManager.add("D", "male", 18, 170, 60);
let e = FitnessManager.add("E", "female", 18, 160, 40);

// 开始评判
a.judge("A"); // A: false
b.judge("B"); // B: false
c.judge("C"); // C: false
d.judge("D"); // D: true
e.judge("E"); // E: true

// factory对象有点像单例模式,只是多了一个sex的参数,如果没有内部状态,则没有参数的factory对象就更接近单例模式了

职责链模式

定义

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

它为止

核心

请求发送者只需要知道链中的第一个节点,弱化发送者和一组接收者之间的强联系,可以便捷地在职责链中增加或删除一个节点,同样地,指定谁是第一个节

点也很便捷

实现

展示不同类型的变量

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
// 定义链的某一项
function ChainItem(fn) {
this.fn = fn;
this.next = null;
}

ChainItem.prototype = {
constructor: ChainItem,

// 设置下一项
setNext: function (next) {
this.next = next;
console.log("next: " + next);
return next;
},

// 开始执行
start: function () {
this.fn.apply(this, arguments);
},

// 转到链的下一项执行
toNext: function () {
if (this.next) {
this.start.apply(this.next, arguments);
} else {
console.log("无匹配的执行项目");
}
},
};

// 展示数字
function showNumber(num) {
if (typeof num === "number") {
console.log("number", num);
} else {
// 转移到下一项
this.toNext(num);
console.log("数字后下一步");
}
}

// 展示字符串
function showString(str) {
if (typeof str === "string") {
console.log("string", str);
} else {
this.toNext(str);
}
}

// 展示对象
function showObject(obj) {
if (typeof obj === "object") {
console.log("object", obj);
} else {
this.toNext(obj);
}
}

let chainNumber = new ChainItem(showNumber);
let chainString = new ChainItem(showString);
let chainObject = new ChainItem(showObject);

// 设置链条
let next1 = chainObject.setNext(chainNumber);
console.log("next1: " + JSON.stringify(next1)); // next1: {"next":{"next":null}}
let next2 = next1.setNext(chainString);
console.log("next2: " + JSON.stringify(next2)); // {"next":null}

chainString.start("12"); // string 12
chainNumber.start({}); // 无匹配的执行项目
chainObject.start({}); // object {}
chainObject.start(123); // number 123

这时想判断未定义的时候呢,直接加到链中即可

1
2
3
4
5
6
7
8
9
10
11
12
13
// 展示未定义
function showUndefined(obj) {
if (typeof obj === "undefined") {
console.log("undefined");
} else {
this.toNext(obj);
}
}

let chainUndefined = new ChainItem(showUndefined);
chainString.setNext(chainUndefined);

chainNumber.start(); // undefined

对象增多,结构更清晰,一定程度上可能影响性能,注意避免过长的职责链

中介者模式

定义

所有的相关 对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可

核心

使网状的多对多关系变成了相对简单的一对多关系(复杂的调度处理都交给中介者)

实现

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
let A = {
score: 10,

changeTo: function (score) {
this.score = score;

// 自己获取
this.getRank();
},

// 直接获取
getRank: function () {
let scores = [this.score, B.score, C.score].sort(function (a, b) {
return a < b;
});

console.log(scores.indexOf(this.score) + 1);
},
};

let B = {
score: 20,

changeTo: function (score) {
this.score = score;

// 通过中介者获取
rankMediator(B);
},
};

let C = {
score: 30,

changeTo: function (score) {
this.score = score;

rankMediator(C);
},
};

// 中介者,计算排名
function rankMediator(person) {
let scores = [A.score, B.score, C.score].sort(function (a, b) {
return a < b;
});

console.log(scores.indexOf(person.score) + 1);
}

// A通过自身来处理
A.changeTo(100); // 1

// B和C交由中介者处理
B.changeTo(200); // 1
C.changeTo(50); // 3

虽然中介者做到了对模块和对象的解耦,但有时对象之间的关系并非一定要解耦,强行使用中介者来整合,可能会使代码更为繁琐,需要注意

装饰者模式

1. 定义

以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责

2. 核心

是为对象动态加入行为,经过多重包装,可以形成一条装饰链

3. 实现

最简单的装饰者,就是重写对象的属性

1
2
3
4
5
let A = {
score: 10,
};

A.score = "分数:" + A.score;

可以使用传统面向对象的方法来实现装饰,添加技能

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
function Person() {}

Person.prototype.skill = function () {
console.log("数学");
};

// 装饰器,还会音乐
function MusicDecorator(person) {
this.person = person;
}

MusicDecorator.prototype.skill = function () {
this.person.skill();
console.log("音乐");
};

// 装饰器,还会跑步
function RunDecorator(person) {
this.person = person;
}

RunDecorator.prototype.skill = function () {
this.person.skill();
console.log("跑步");
};

let person = new Person();

// 装饰一下
let person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);

person.skill(); // 数学
person1.skill(); // 数学 音乐 跑步

更简洁的写法

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
// 装饰器,在当前函数执行前先执行另一个函数
function decoratorBefore(fn, beforeFn) {
return function () {
let ret = beforeFn.apply(this, arguments);

// 在前一个函数中判断,不需要执行当前函数
if (ret !== false) {
fn.apply(this, arguments);
}
};
}

function skill() {
console.log("数学");
}

function skillMusic() {
console.log("音乐");
}

function skillRun() {
console.log("跑步");
}

let skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);

skillDecorator(); // 跑步 音乐 数学

状态模式

1. 定义

事物内部状态的改变往往会带来事物的行为改变。在处理的时候,将这个处理委托给当前的 状态对象 即可,该状态对象会负责渲染它自身的行为

2. 核心

区分事物内部的状态,把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部

3. 实现

以一个人的工作状态作为例子,在刚醒、精神、疲倦几个状态中切换着

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
// 工作状态
function Work(name) {
this.name = name;
this.currentState = null;

// 工作状态,保存为对应状态对象
this.wakeUpState = new WakeUpState(this);
// 精神饱满
this.energeticState = new EnergeticState(this);
// 疲倦
this.tiredState = new TiredState(this);

this.init();
}

Work.prototype.init = function () {
this.currentState = this.wakeUpState;

// 点击事件,用于触发更新状态
document.body.onclick = () => {
this.currentState.behaviour();
};
};

// 更新工作状态
Work.prototype.setState = function (state) {
this.currentState = state;
};

// 刚醒
function WakeUpState(work) {
this.work = work;
}

// 刚醒的行为
WakeUpState.prototype.behaviour = function () {
console.log(this.work.name, ":", "刚醒呢,睡个懒觉先");

// 只睡了2秒钟懒觉就精神了..
setTimeout(() => {
this.work.setState(this.work.energeticState);
}, 2 * 1000);
};

// 精神饱满
function EnergeticState(work) {
this.work = work;
}

EnergeticState.prototype.behaviour = function () {
console.log(this.work.name, ":", "超级精神的");

// 才精神1秒钟就发困了
setTimeout(() => {
this.work.setState(this.work.tiredState);
}, 1000);
};

// 疲倦
function TiredState(work) {
this.work = work;
}

TiredState.prototype.behaviour = function () {
console.log(this.work.name, ":", "怎么肥事,好困");

// 不知不觉,又变成了刚醒着的状态... 不断循环呀
setTimeout(() => {
this.work.setState(this.work.wakeUpState);
}, 1000);
};

let work = new Work("曹操");

4. 优缺点

优点

状态切换逻辑分布在状态类中,易于维护

缺点

多个状态类,影响性能,可用享元模式进一步优化

将逻辑分散在状态类中,不易看出状态机变化逻辑

十四、适配器模式

定义

是解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配

实现

比如一个简单的数据格式转换的适配器

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
// 渲染数据,格式限制为数组了
function renderData(data) {
data.forEach(function (item) {
console.log(item);
});
}

// 对非数组的进行转换适配
function arrayAdapter(data) {
if (typeof data !== "object") {
return [];
}

if (Object.prototype.toString.call(data) === "[object Array]") {
return data;
}

let temp = [];

for (let item in data) {
// hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)
// 原型链上继承过来的属性无法通过 hasOwnProperty 检测到
if (data.hasOwnProperty(item)) {
temp.push(data[item]);
}
}

return temp;
}

let data = {
0: "A",
1: "B",
2: "C",
};

renderData(arrayAdapter(data)); // A B C

外观模式

1. 定义

为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使子系统更加容易使用

2. 核心

可以通过请求外观接口来达到访问子系统,也可以选择越过外观来直接访问子系统

3. 实现

外观模式在 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
// 三个处理函数
function start() {
console.log("start");
}

function doing() {
console.log("doing");
}

function end() {
console.log("end");
}

// 外观函数,将一些处理统一起来,方便调用
function execute() {
start();
doing();
end();
}

// 调用init开始执行
function init() {
// 此处直接调用了高层函数,也可以选择越过它直接调用相关的函数
execute();
}

init(); // start doing end