
多态
同一操作作用于不同的对象上面,可以产生不同的解释和不同的执行结果;给不同的对象发送同一个消息的时候,这些对象会根据这个消息分别给出不同的反馈;多态背后的思想是将“做什么”和“谁去做以及怎样去做”分离开来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 不变的部分
function makeSound(animal) {
animal.sound();
}
// 可变的部分,将行为分布在各个对象中,并让这些对象各自负责自己的行为
const Chicken = function () {};
const Duck = function () {};
Chicken.prototype.sound = function () {
console.log('咯咯咯');
};
Duck.prototype.sound = function () {
console.log('嘎嘎嘎');
};
makeSound(new Chicken()); // 咯咯咯
makeSound(new Duck()); // 嘎嘎嘎
|
面向接口编程
“在系统分析和架构中,分清层次和依赖关系;每个层次不是直接向其上层提供服务,而是通过定义一组接口,仅向上层暴露其接口功能;上层对于下层仅仅是接口依赖,而不依赖具体类”是设计模式中最重要的思想。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Duck {
makeSound() {
console.log('嘎嘎嘎');
}
}
class Chicken {
makeSound() {
console.log('咯咯咯');
}
}
class AnimalSound {
makeSound(animal) {
animal.makeSound();
}
}
const duck = new Duck();
const chicken = new Chicken();
const animalSound = new AnimalSound();
animalSound.makeSound(duck); // 嘎嘎嘎
animalSound.makeSound(chicken); // 咯咯咯
|
把客户的业务逻辑线提取出来,作为接口;业务具体实现通过该接口的实现类来完成;当客户需求变化时,只需编写该业务逻辑的新的实现类:
- 降低程序的耦合性;
- 易于程序的扩展;
- 有利于程序的维护。
面向切面编程(AOP)
把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。
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.prototype.before = function (beforeFn) {
const originalFn = this;
return function () {
beforeFn.call(this, arguments);
const originalResult = originalFn.call(this, arguments);
return originalResult;
};
};
Function.prototype.after = function (afterFn) {
const originalFn = this;
return function () {
const originalResult = originalFn.call(this, arguments);
afterFn.call(this, arguments);
return originalResult;
};
};
let fn = function () {
console.log('fn');
};
// before -> fn -> after
fn = fn
.before(function () {
console.log('before');
})
.after(function () {
console.log('after');
});
fn();
|
单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
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
|
const Single = (function () {
let instance;
return function () {
if (instance) {
return instance;
}
instance = this;
return instance;
};
})();
console.log(new Single() === new Single());
// 创建全局唯一的登录弹窗
function getSingle(fn) {
let instance;
return function () {
return instance || (instance = fn.apply(this, arguments));
};
}
function LoginLayer() {
const div = document.createElement('div');
div.innerHTML = '登录';
div.style.display = 'none';
document.body.appendChild(div);
return div;
}
getSingle(LoginLayer)();
|
策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
const strategies = {
S(salary) {
return salary * 4;
},
A(salary) {
return salary * 3;
},
B(salary) {
return salary * 2;
}
};
const calculateBonus = function (level, salary) {
return strategies[level](salary);
};
console.log(calculateBonus('S', 20000));
|
- 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句;
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作;
- 在策略模式中利用组合和委托来让 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
|
// 使用虚拟代理懒加载图片
const myImage = (function () {
const imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return {
setSrc(src) {
imgNode.src = src;
}
};
})();
const proxyImage = (function () {
const temp = new Image();
temp.onload = function () {
myImage.setSrc(this.src);
};
return {
setSrc(src) {
myImage.setSrc('./loading.gif');
temp.src = src;
}
};
})();
proxyImage.setSrc(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png'
);
|
代理和本体接口的一致性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
function syncFile(id) {
console.log(`同步文件:${id}`);
}
const proxySyncFile = (function () {
let temp = [];
let timer;
return function (id) {
temp.push(id);
if (timer) {
return;
}
timer = setTimeout(() => {
syncFile(temp.join(','));
clearTimeout(timer);
timer = null;
temp = [];
}, 2000);
};
})();
|
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function mult() {
let temp = 1;
for (let i = 0; i < arguments.length; i++) {
temp *= arguments[i];
}
return temp;
}
const proxtMult = (function (fn) {
const cache = {};
return function () {
const key = [...arguments].join(',');
if (!cache[key]) {
cache[key] = fn.apply(this, arguments);
}
return cache[key];
};
})(mult);
|
迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。
1
2
3
4
5
6
7
|
function Iterator(o) {
let current = 0;
const next = () => (current += 1);
const isDone = () => current >= o.length;
const getCurrent = () => o[current];
return [next, isDone, getCurrent];
}
|
发布订阅模式
可以取代对象之间硬编码的通知机制,一个对象不用再显式地调用另外一个对象的某个接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
const event = {
clientList: {},
listen(key, fn) {
this.clientList[key]
? this.clientList[key].push(fn)
: (this.clientList[key] = [fn]);
},
trigger(key, ...rest) {
const fns = this.clientList[key];
if (Array.isArray(fns) && fns.length > 0) {
for (let i = 0; i < fns.length; i++) {
fns[i].apply(this, rest);
}
}
},
remove(key, fn) {
const fns = this.clientList[key];
if (Array.isArray(fns) && fns.length > 0) {
const index = fns.findIndex(fn);
this.clientList[key].splice(index, 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
|
const btn1 = document.getElementById('btn1');
const btn2 = document.getElementById('btn2');
const btn3 = document.getElementById('btn3');
const MenuBar = {
refresh() {
console.log('refresh');
}
};
const RefreshCommand = function (receiver) {
return {
execute() {
receiver.refresh();
}
};
};
const refreshMenuBarCommand = RefreshCommand(MenuBar);
function setCommand(ele, command) {
ele.onclick = function () {
command.execute();
};
}
setCommand(btn1, refreshMenuBarCommand);
setCommand(btn2, refreshMenuBarCommand);
setCommand(btn3, refreshMenuBarCommand);
|
组合模式
将对象组合成树形结构,以表示“部分-整体”的层次结构;通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性。
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
|
const openAcCommand = {
add() {
throw new Error('add 无效');
},
execute() {
console.log('打开空调');
}
};
const openTvCommand = {
add() {
throw new Error('add 无效');
},
execute() {
console.log('打开电视');
}
};
const openSoundCommand = {
add() {
throw new Error('add 无效');
},
execute() {
console.log('打开音响');
}
};
const openPcCommand = {
add() {
throw new Error('add 无效');
},
execute() {
console.log('打开电脑');
}
};
const closeDoorCommand = {
add() {
throw new Error('add 无效');
},
execute() {
console.log('关门');
}
};
function MacroCommand() {
return {
commandList: [],
add(command) {
this.commandList.push(command);
},
execute() {
for (var i = 0, command; (command = this.commandList[i++]); ) {
command.execute();
}
}
};
}
const macroCommand1 = MacroCommand();
macroCommand1.add(openTvCommand);
macroCommand1.add(openSoundCommand);
const macroCommand2 = MacroCommand();
macroCommand2.add(openPcCommand);
macroCommand2.add(closeDoorCommand);
const macroCommand = MacroCommand();
macroCommand.add(openAcCommand);
macroCommand.add(macroCommand1);
macroCommand.add(macroCommand2);
macroCommand.execute();
|
组合对象包含一组叶对象,但 Leaf 并不是 Composite 的子类;组合对象把请求委托给它所包含的所有叶对象,它们能够合作的关键是拥有相同的接口。
组合模式除了要求组合对象和叶对象拥有相同的接口之外,还有一个必要条件,就是对一组叶对象的操作必须具有一致性。
模板方法
由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类;通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序;子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
享元模式(flyweight)
运用共享技术来有效支持大量细粒度的对象;如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function Model(sex) {
this.sex = sex; // 内部状态存储于对象内部,可以被一些对象共享,独立于具体的场景
}
Model.prototype.takePhoto = function () {
console.log(`sex: ${this.sex}, underwear: ${this.underwear}`);
};
const maleModel = new Model('male');
const femaleModel = new Model('female');
for (let i = 0; i < 50; i++) {
maleModel.underwear = `underwear${i}`; // 外部状态
maleModel.takePhoto();
}
for (let j = 0; j < 50; j++) {
femaleModel.underwear = `underwear${j}`; // 取决于具体的场景,并根据场景而变化,不能被共享
femaleModel.takePhoto();
}
|
实现享元模式的关键是把内部状态和外部状态分离开来,有多少种内部状态的组合,系统中便最多存在多少个共享对象;而外部状态储存在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
const toopTipFactory = (function () {
const toolTipPool = []; // toolTip 对象池
return {
create() {
if (toolTipPool.length === 0) {
const div = document.createElement('div');
document.body.appendChild(div);
return div;
} else {
return toolTipPool.shift();
}
},
recover(toolTipDom) {
return toolTipPool.push(toolTipDom);
}
};
})();
|
职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系;将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止;我们把这些对象称为链中的节点。
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
|
function Chain(fn) {
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function (successor) {
return (this.successor = successor);
}; // 下一个节点
Chain.prototype.passRequest = function () {
const ret = this.fn.apply(this, arguments);
if (ret === 'nextSuccessor') {
return (
this.successor &&
this.successor.passRequest.apply(this.successor, arguments)
);
}
return ret;
}; // 传递请求给某个节点
Chain.prototype.next = function () {
return (
this.successor &&
this.successor.passRequest.apply(this.successor, arguments)
);
}; // 可以用来异步传递
function order500(orderType, pay, stock) {
if (orderType === 1 && pay && stock > 0) {
console.log('500 元定金,返 100 元优惠券');
} else {
return 'nextSuccessor'; // 处理不了,传给下一个
}
}
function order200(orderType, pay, stock) {
if (orderType === 2 && pay && stock > 0) {
console.log('200 元定金,返 50 元优惠券');
} else {
return 'nextSuccessor'; // 处理不了,传给下一个
}
}
function orderNormal(orderType, pay, stock) {
if (stock > 0) {
console.log('无优惠券');
} else {
console.log('库存不足');
}
}
const chainOrder500 = new Chain(order500);
const chainOrder200 = new Chain(order200);
const chainOrderNormal = new Chain(orderNormal);
chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);
chainOrder500.passRequest(1, true, 1000);
chainOrder500.passRequest(2, true, 1000);
chainOrder500.passRequest(3, true, 1000);
chainOrder500.passRequest(1, true, 0);
|
职责链模式的最大优点就是解耦了请求发送者和 N 个接收者之间的复杂关系;由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可。
用 AOP 实现职责链:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Function.prototype.after = function (fn) {
const self = this;
return function () {
const ret = self.apply(this, arguments);
if (ret === 'nextSuccessor') {
return fn.apply(this, arguments);
}
return ret;
};
};
const order = order500.after(order200).after(orderNormal);
order(1, true, 1000);
order(2, true, 1000);
order(3, true, 1000);
order(1, true, 0);
|
中介者模式
解除对象与对象之间的紧耦合关系;增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用;所以当一个对象发生改变时,只需要通知中介者对象即可。
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
|
const playerDirector = (function () {
const players = {}; // 保存所有玩家
const operations = {}; // 中介者可以执行的操作
operations.addPlayer = function (player) {
const teamColor = player.teamColor;
players[teamColor] = players[teamColor] || [];
players[teamColor].push(player);
};
operations.removePlayer = function (player) {
const teamColor = player.teamColor;
const teamPlayers = players[teamColor] || [];
for (var i = teamPlayers.length - 1; i >= 0; i--) {
if (teamPlayers[i] === player) {
teamPlayers.splice(i, 1);
}
}
};
operations.changeTeam = function (player, teamColor) {
operations.removePlayer(player);
player.teamColor = teamColor;
operations.addPlayer(player);
};
operations.playerDead = function (player) {
const teamColor = player.teamColor;
const teamPlayers = players[teamColor] || [];
let allDead = true;
player.state = 'dead';
for (var i = 0, player; (player = teamPlayers[i++]); ) {
if (player.state !== 'dead') {
allDead = false;
break;
}
}
if (allDead === true) {
// 全部死亡
for (var i = 0, player; (player = teamPlayers[i++]); ) {
player.lose(); // 本队所有玩家 lose
}
for (var color in players) {
if (color !== teamColor) {
var otherTeamPlayers = players[color]; // 其他队伍的玩家
for (var i = 0, player; (player = otherTeamPlayers[i++]); ) {
player.win(); // 其他队伍所有玩家 win
}
}
}
}
};
return {
reciveMessage(msg, ...rest) {
operations[msg].apply(this, rest);
}
};
})();
function Player(name, teamColor) {
this.name = name;
this.state = 'alive';
this.teamColor = teamColor;
}
Player.prototype.win = function () {
console.log(`${this.name} won`);
};
Player.prototype.lose = function () {
console.log(`${this.name} lose`);
};
Player.prototype.die = function () {
this.state = 'die';
// 给中介者发送“玩家死亡”
playerDirector.reciveMessage('playerDead', this);
};
Player.prototype.remove = function () {
// 给中介者发送“移除玩家”
playerDirector.reciveMessage('removePlayer', this);
};
Player.prototype.changeTeam = function (teamColor) {
// 给中介者发送“玩家换队”
// this.teamColor = teamColor;
playerDirector.reciveMessage('changeTeam', this, teamColor);
};
function playerFactory(name, teamColor) {
var player = new Player(name, teamColor); // 创造一个新的玩家对象
playerDirector.reciveMessage('addPlayer', player); // 给中介者发送消息,新增玩家
return player;
}
|
中介者模式使各个对象之间得以解耦,以中介者和对象之间的一对多关系取代了对象之间的网状多对多关系;各个对象只需关注自身功能的实现,对象之间的交互关系交给了中介者对象来实现和维护。
最大的缺点是系统中会新增一个中介者对象,因为对象之间交互的复杂性,转移成了中介者对象的复杂性,使得中介者对象经常是巨大的;中介者对象自身往往就是一个难以维护的对象。
装饰者模式
给对象动态地增加职责的方式称为装饰者模式;能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责;跟继承相比,装饰者是一种更轻便灵活的做法,这是一种“即用即付”的方式;他没有真正地改动对象自身,而是将对象放入另一个对象之中,这些对象以一条链的方式进行引用,形成一个聚合对象。
状态模式
状态模式的关键是区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变;状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部。
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
|
function OffLightState(light) {
this.light = light;
}
function WeakLightState() {
this.light = light;
}
function StrongLightState() {
this.light = light;
}
OffLightState.prototype.buttonWasPressed = function () {
console.log('关灯->弱光');
this.light.setState(this.light.weakLightState);
};
WeakLightState.prototype.buttonWasPressed = function () {
console.log('弱光->强光');
this.light.setState(this.light.strongLightState);
};
StrongLightState.prototype.buttonWasPressed = function () {
console.log('强光->关灯');
this.light.setState(this.light.offLightState);
};
function Light() {
this.button = null;
this.offLightState = new OffLightState(this);
this.weakLightState = new WeakLightState(this);
this.strongLightState = new StrongLightState(this);
}
Light.prototype.setState = function (state) {
this.currentState = state;
};
Light.prototype.init = function () {
const button = document.createElement('button');
const self = this;
this.button = document.body.appendChild(button);
this.button.innerHTML = '开关';
this.currentState = this.offLightState;
this.button.onclick = function () {
self.currentState.buttonWasPressed();
};
};
|
- 状态模式定义了状态与行为之间的关系,并将它们封装在一个类里;通过增加新的状态类,很容易增加新的状态和转换;
- 避免 Context 无限膨胀,状态切换的逻辑被分布在状态类中;
- 用对象代替字符串来记录当前状态,使得状态的切换更加一目了然;
- Context 中的请求动作和状态类中封装的行为可以非常容易地独立变化而互不影响。
状态模式的缺点是会在系统中定义许多状态类,编写多个状态类是一项枯燥乏味的工作,而且系统中会因此而增加不少对象;由于逻辑分散在状态类中,虽然避开了条件分支语句,但也造成了逻辑分散的问题;我们无法在一个地方就看出整个状态机的逻辑。
适配器模式
适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化;适配器模式不需要改变已有的接口,就能够使它们协同作用。