设计模式及js实现(三)行为型模式

23种设计模式根据其目的(模式是用来做什么的)可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种:

  • 创建型模式主要用于创建对象。
  • 结构型模式主要用于处理类或对象的组合。
  • 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责。

目录

  • 行为型模式
      - 职责链模式(Chain of Responsibility)
      - 命令模式(Command)
      - 解释器模式(Interpreter)
      - 迭代器模式(Iterator)
      - 中介者模式(Mediator)
      - 备忘录模式(Memento)
      - 观察者模式(Observer)
      - 状态模式(State)
      - 策略模式(Strategy)
      - 模板方法模式(Template Method)
      - 访问者模式(Visitor)

行为型模式

职责链模式(Chain of Responsibility)

chain.png

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。责任链模式:多个对象均有机会处理请求,从而解除发送者和接受者之间的耦合关系。这些对象连接成为链式结构,每个节点转发请求,直到有对象处理请求为止。

优缺点

  • 优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
  • 缺点: 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
var MoneyStack = function(billSize) {
this.billSize = billSize;
this.next = null;
};
MoneyStack.prototype = {
withdraw: function(amount) {
var numOfBills = Math.floor(amount / this.billSize);
if (numOfBills > 0) {
// Eject the bills
this._ejectMoney(numOfBill);
// Shrink the amount by how much money we ejected
amount = amount - (this.billSize * numOfBills);
}
// If there is any money left to withdraw and if we have
// another stack in the line, pass the request on
amount > 0 && this.next && this.next.withdraw(amount);
},
// set the stack that comes next in the chain
setNextStack: function(stack) {
this.next = stack;
},
// private method that ejects the money
_ejectMoney: function(numOfBills) {
console.log(numOfBills + " $" + this.billSize
+ " bill(s) has/have been spit out");
}
};
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
var ATM = function() {
// Create the stacks of money
// We'll show you the implementation for this next
var stack100 = new MoneyStack(100),
stack50 = new MoneyStack(50),
stack20 = new MoneyStack(20),
stack10 = new MoneyStack(10),
stack5 = new MoneyStack(5),
stack1 = new MoneyStack(1);
// Set the hierarchy for the stacks
stack100.setNextStack(stack50);
stack50.setNextStack(stack20);
stack20.setNextStack(stack10);
stack10.setNextStack(stack5);
stack5.setNextStack(stack1);
// Set the top stack as a property
this.moneyStacks = stack100;
}
ATM.prototype.withdraw = function(amount) {
this.moneyStacks.withdraw(amount);
}
// USAGE
var atm = new ATM();
atm.withdraw(186);
/* outputs:
1 $100 bill(s) has/have been spit out
1 $50 bill(s) has/have been spit out
1 $20 bill(s) has/have been spit out
1 $10 bill(s) has/have been spit out
1 $5 bill(s) has/have been spit out
1 $1 bill(s) has/have been spit out
*/
atm.withdraw(72);
/* outputs:
1 $50 bill(s) has/have been spit out
1 $20 bill(s) has/have been spit out
2 $1 bill(s) has/have been spit out
*/

ES6实现

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
class Handler {
constructor() {
this.next = null;
}

setNext(handler) {
this.next = handler;
}
}

class LogHandler extends Handler {
constructor(...props) {
super(...props);
this.name = "log";
}

handle(level, msg) {
if (level === this.name) {
console.log(`LOG: ${msg}`);
return;
}
this.next && this.next.handle(...arguments);
}
}

class WarnHandler extends Handler {
constructor(...props) {
super(...props);
this.name = "warn";
}

handle(level, msg) {
if (level === this.name) {
console.log(`WARN: ${msg}`);
return;
}
this.next && this.next.handle(...arguments);
}
}

class ErrorHandler extends Handler {
constructor(...props) {
super(...props);
this.name = "error";
}

handle(level, msg) {
if (level === this.name) {
console.log(`ERROR: ${msg}`);
return;
}
this.next && this.next.handle(...arguments);
}
}

/******************以下是测试代码******************/

let logHandler = new LogHandler();
let warnHandler = new WarnHandler();
let errorHandler = new ErrorHandler();

// 设置下一个处理的节点
logHandler.setNext(warnHandler);
warnHandler.setNext(errorHandler);

logHandler.handle("error", "Some error occur");

命令模式(Command)

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。命令模式是一种数据驱动的设计模式,它属于行为型模式。

命令模式的执行步骤:

  • 1.请求以命令的形式包裹在对象中,并传给调用对象。
  • 2.调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象。
  • 3.该对象执行命令。

在这三步骤中,分别有 3 个不同的主体:发送者、传递者和执行者。在实现过程中,需要特别关注。

优缺点

  • 优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
  • 缺点:使用命令模式可能会导致某些系统有过多的具体命令类。

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
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button id="replay">播放录像</button>
<script>
let Ryu = {
attack: function(){
console.log( '攻击' )
},
defense: function(){
console.log( '防御' )
},
jump: function(){
console.log( '跳跃' )
},
crouch: function(){
console.log( '蹲下' )
}
}
let makeCommand = function( receiver, state ){ // 创建命令
return function(){
receiver[ state ]()
}
}
let commands = {
"119": "jump", // W
"115": "crouch", // S
"97": "defense", // A
"100": "attack" // D
}
let commandStack = [] // 保存命令的堆栈
document.onkeypress = function( ev ){
let keyCode = ev.keyCode,
command = makeCommand( Ryu, commands[ keyCode ] )
if ( command ){
command() // 执行命令
commandStack.push( command ) // 将刚刚执行过的命令保存进堆栈
}
}

document.getElementById( 'replay' ).onclick = function(){ // 点击播放录像
let command
while( command = commandStack.shift() ){ // 从堆栈里依次取出命令并执行
command()
}
}
</script>
</body>
</html>

ES6实现

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 MenuBar = {
refresh() {
console.log("刷新菜单页面");
}
};

// 命令对象,execute方法就是执行相关命令
const RefreshMenuBarCommand = receiver => {
return {
execute() {
receiver.refresh();
}
};
};

// 为按钮对象指定对应的 对象
const setCommand = (button, command) => {
button.onclick = () => {
command.execute();
};
};

let refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar);
let button = document.querySelector("button");
setCommand(button, refreshMenuBarCommand);

解释器模式(Interpreter)

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。解释器模式: 提供了评估语言的语法或表达式的方式。

这是基本不怎么使用的一种设计模式。 确实想不到什么场景一定要用此种设计模式。

实现这种模式的核心是:

  • 抽象表达式:主要有一个interpret()操作
    • 终结符表达式:R = R1 + R2中,R1 R2就是终结符
    • 非终结符表达式:R = R1 - R2中,-就是终结符
  • 环境(Context): 存放文法中各个终结符所对应的具体值。比如前面R1和R2的值。

优缺点

  • 优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。
  • 缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。

js实现

ES6实现

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
class Context {
constructor() {
this._list = []; // 存放 终结符表达式
this._sum = 0; // 存放 非终结符表达式(运算结果)
}

get sum() {
return this._sum;
}

set sum(newValue) {
this._sum = newValue;
}

add(expression) {
this._list.push(expression);
}

get list() {
return [...this._list];
}
}

class PlusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = ++context.sum;
}
}

class MinusExpression {
interpret(context) {
if (!(context instanceof Context)) {
throw new Error("TypeError");
}
context.sum = --context.sum;
}
}

/** 以下是测试代码 **/

const context = new Context();

// 依次添加: 加法 | 加法 | 减法 表达式
context.add(new PlusExpression());
context.add(new PlusExpression());
context.add(new MinusExpression());

// 依次执行: 加法 | 加法 | 减法 表达式
context.list.forEach(expression => expression.interpret(context));
console.log(context.sum);

迭代器模式(Iterator)

迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式是指提供一种方法顺序访问一个集合对象的各个元素,使用者不需要了解集合对象的底层实现。

优缺点

  • 优点: 1、它支持以不同的方式遍历一个聚合对象。 2、迭代器简化了聚合类。 3、在同一个聚合上可以有多个遍历。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
  • 缺点:由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。

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
45
46
// IE 上传控件
var getActiveUploadObj = function() {
try {
return new ActiveXObject("TXFTNActiveX.FTNUpload");
} catch (e) {
return false;
}
};

// Flash 上传
var getFlashUploadObj = function() {
if (supportFlash()) { // supportFlash 函数未提供
var str = '<object type="application/x-shockwave-flash"></object>';
return $(str).appendTo($('body'));
};
return false;
}

// 表单上传
var getFormUpladObj = function() {
var str = '<input name="file" type="file" class="ui-file"/>';
return $(str).appendTo($('body'));
};

var iteratorUpload = function() {
for (var i = 0, fn; fn = arguments[i++];) {
var upload = fn();
if (upload !== false) {
return upload;
}
}
};

var uploadObj = iteratorUpload( getActiveUploadObj, getFlashUploadObj, getFormUpladObj );

var getWebkitUploadObj = function(){
// 具体代码略
}

var getHtml5UploadObj = function(){
// 具体代码略
}

// 依照优先级把它们添加进迭代器
var uploadObj = iteratorUploadObj( getActiveUploadObj, getWebkitUploadObj,
getFlashUploadObj, getHtml5UploadObj, getFormUpladObj );

ES6实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Iterator = obj => {
let current = 0;
let next = () => (current += 1);
let end = () => current >= obj.length;
let get = () => obj[current];

return {
next,
end,
get
};
};

let myIter = Iterator([1, 2, 3]);
while (!myIter.end()) {
console.log(myIter.get());
myIter.next();
}

中介者模式(Mediator)

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。

优缺点

  • 优点: 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
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
var Participant = function(name) {
this.name = name;
this.chatroom = null;
};
Participant.prototype = {
send: function(message, to) {
this.chatroom.send(message, this, to);
},
receive: function(message, from) {
log.add(from.name + " to " + this.name + ": " + message);
}
};
var Chatroom = function() {
var participants = {};

return {

register: function(participant) {
participants[participant.name] = participant;
participant.chatroom = this;
},

send: function(message, from, to) {
if (to) {
to.receive(message, from);
} else {
for (key in participants) {
if (participants[key] !== from) {
participants[key].receive(message, from);
}
}
}
}
};
};
var log = (function() {
var log = "";

return {
add: function(msg) { log += msg + "\n"; },
show: function() { alert(log); log = ""; }
}
})();

function run() {
var yoko = new Participant("Yoko");
var john = new Participant("John");
var paul = new Participant("Paul");
var ringo = new Participant("Ringo");
var chatroom = new Chatroom();
chatroom.register(yoko);
chatroom.register(john);
chatroom.register(paul);
chatroom.register(ringo);
yoko.send("All you need is love.");
yoko.send("I love you John.");
john.send("Hey, no need to broadcast", yoko);
paul.send("Ha, I heard that!");
ringo.send("Paul, what do you think?", paul);
log.show();
}
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>中介者模式 购买商品</title>
</head>
<body>
选择颜色:
<select id="colorSelect">
<option value="">请选择</option>
<option value="red">红色</option>
<option value="blue">蓝色</option>
</select>

选择内存:
<select id="memorySelect">
<option value="">请选择</option>
<option value="32G">32G</option>
<option value="16G">16G</option>
</select>

输入购买数量:
<input type="text" id="numberInput">

您选择了颜色:<div id="colorInfo"></div><br>
您选择了内存:<div id="memoryInfo"></div><br>
您输入了数量:<div id="numberInfo"></div><br>

<button id="nextBtn" disabled>请选择手机颜色、内存和购买数量</button>
</body>
<script>
var goods = {
'red|32G': 3,
'red|16G': 0,
'blue|32G': 1,
'blue|16G': 6
}

//引入中介者
var mediator = (function(){
var colorSelect = document.getElementById('colorSelect'),
memorySelect = document.getElementById('memorySelect'),
numberInput = document.getElementById('numberInput'),
colorInfo = document.getElementById('colorInfo'),
memoryInfo = document.getElementById('memoryInfo'),
numberInfo = document.getElementById('numberInfo'),
nextBtn = document.getElementById('nextBtn');

return {
changed: function(obj){
var color = colorSelect.value,
memory = memorySelect.value,
number = numberInput.value,
stock = goods[color + '|' + memory];

if(obj == colorSelect){ //如果改变的是选择颜色下拉框
colorInfo.innerHTML = color;
}else if(obj == memorySelect){
memoryInfo.innerHTML = memory;
}else if(obj == numberInput){
numberInfo.innerHTML = number;
}

if(!color){
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机颜色';
return;
}

if(!memory){
nextBtn.disabled = true;
nextBtn.innerHTML = '请选择手机内存';
return;
}

if(!number){
nextBtn.disabled = true;
nextBtn.innerHTML = '请填写手机数量';
return;
}

if( ( (number-0) | 0 ) !== number-0 ){ //用户输入的购买数量是否为正整数
nextBtn.disabled = true;
nextBtn.innerHTML = '请输入正确的购买数量';
return;
}

if(number > stock){ //当前选择数量大于库存量
nextBtn.disabled = true;
nextBtn.innerHTML = '库存不足';
return;
}

nextBtn.disabled = false;
nextBtn.innerHTML = '放入购物车';
}
}
})()

colorSelect.onchange = function(){
mediator.changed(this)
}

memorySelect.onchange = function(){
mediator.changed(this)
}

numberInput.oninput = function(){
mediator.changed(this)
}

//以后如果想要再增加选项,如手机CPU之类的,只需在中介者对象里加上相应配置即可。
</script>
</html>

备忘录模式(Memento)

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。它属于行为模式,保存某个状态,并且在需要的时候直接获取,而不是重复计算。

备忘录模式实现,不能破坏原始封装。 也就是说,能拿到内部状态,将其保存在外部。

优缺点

  • 优点: 1、给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。 2、实现了信息的封装,使得用户不需要关心状态的保存细节。
  • 缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。

js实现

原:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 事件:下一页
$('#next_page_btn').click(function() {
// 获取新闻列表容器
var $news = $('#news_content');
// 获取当前页号
var page = $news.data('page');
// 从服务端获取下一页的列表数据并显示
getPageDataFromServer(page, function() {
$news.data('page', page+1);
})
});
// 事件:上一页
$('#pre_page_btn').click(function() {
// 获取新闻列表容器
var $news = $('#news_content');
// 获取当前页号
var page = $news.data('page');
// 从服务端获取上一页的列表数据并显示
getPageDataFromServer(page, function() {
$news.data('page', page-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
// Page备忘录
var Page = function() {
// 缓存器
var cache = {};
return function(page, fn) {
if (cache[page]) {
// 1-如果缓存中存在指定页的数据,直接使用
showPage(page, cache[page]);
fn && fn();
} else {
// 2-如果缓存中不存在指定页的数据,则从服务端获取,并将其缓存下来
$.post('server_api_url', {page: page}, function(res) {
if (res.success) {
showPage(page, res.data);
cache[page] = res.data;
fn && fn();
} else {
// ajax error
}
});
}
}
}();

// 事件:下一页
$('#next_page_btn').click(function() {
// 获取新闻列表容器
var $news = $('#news_content');
// 获取当前页号
var page = $news.data('page');
Page(page, function() {
$news.data('page', page+1);
});
});
// 事件:上一页
$('#pre_page_btn').click(function() {
// 获取新闻列表容器
var $news = $('#news_content');
// 获取当前页号
var page = $news.data('page');
Page(page, function() {
$news.data('page', page-1);
});
});

观察者模式(Observer)

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

优缺点

  • 优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
  • 缺点: 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const event = {
clientList: [],
listen: function(key , fn) {
if (this.clientListen[key]) {
this.clientList[key] = []
}
this.clientList[key].push(fn)
},
trigger: function() {
const key = Array.prototype.shift.call(arguments)
const fns = this.clientList[key]
if (!fns || fns.length === 0 ) {
return false
}
for (let i = 0, fn ;fn = fns[i++];) {
fn.apply(this, arguments)
}
},
remove : function(key , fn) {
const fns = this.clientList[key]
if (!fns) {
return false
}
if (!fn) {
fns && (fns.length = 0)
} else {
for (let l = fns.length - 1; l>=0; l--) {
const _fn = fns[l]
if ( _fn ===fn) {
fns.splice(l, 1)
}
}
}
}

const installEvent = (obj) => {
for (let i in event) {
obj[i] = event[i]
}
};

const events = {}
installEvent(events)
// 订阅信息
events.listen('newMessage',fn1 = (say) => {
console.log('say:' + say)
})
// 发布信息
events.trigger('newMessage',"Hello world")
//移除订阅
events.remove('newMessage',fn1)

ES6实现

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
/**
* 发布订阅模式(观察者模式)
* handles: 事件处理函数集合
* on: 订阅事件
* emit: 发布事件
* off: 删除事件
**/

class PubSub {
constructor() {
this.handles = {};
}

// 订阅事件
on(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
this.handles[eventType] = [];
}
if (typeof handle == 'function') {
this.handles[eventType].push(handle);
} else {
throw new Error('缺少回调函数');
}
return this;
}

// 发布事件
emit(eventType, ...args) {
if (this.handles.hasOwnProperty(eventType)) {
this.handles[eventType].forEach((item, key, arr) => {
item.apply(null, args);
})
} else {
throw new Error(`"${eventType}"事件未注册`);
}
return this;
}

// 删除事件
off(eventType, handle) {
if (!this.handles.hasOwnProperty(eventType)) {
throw new Error(`"${eventType}"事件未注册`);
} else if (typeof handle != 'function') {
throw new Error('缺少回调函数');
} else {
this.handles[eventType].forEach((item, key, arr) => {
if (item == handle) {
arr.splice(key, 1);
}
})
}
return this; // 实现链式操作
}
}

// 下面做一些骚操作
let callback = function () {
console.log('you are so nice');
}

let pubsub = new PubSub();
pubsub.on('completed', (...args) => {
console.log(args.join(' '));
}).on('completed', callback);

pubsub.emit('completed', 'what', 'a', 'fucking day');
pubsub.off('completed', callback);
pubsub.emit('completed', 'fucking', 'again');

状态模式(State)

在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。状态模式:对象行为是基于状态来改变的。

优缺点

  • 优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

  • 缺点: 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//我们首先定义一个环境类,在这里也就是菜单对象,拥有一个状态成员,可以修改其状态并作出相应反应
var trafficLight = (function () {
var currentLight = null;
return {
change: function (light) {
currentLight = light;
console.log(currentLight) ---对应各自的实例对象
currentLight.go(); ---实例对象的方法
}
}
})();

//我们首先定义状态类,表示一种状态,包含其相应的处理方法
function RedLight() { }
RedLight.prototype.go = function () {
console.log("this is red light");
}
function GreenLight() { }
GreenLight.prototype.go = function () {
console.log("this is green light");
}
function YellowLight() { }
YellowLight.prototype.go = function () {
console.log("this is yellow light");
}

trafficLight.change(new RedLight()); //----this is red light
trafficLight.change(new YellowLight()); //----this is yellow light

//我们首先定义一个环境类,在这里也就是菜单对象,拥有一个状态成员,可以修改其状态并作出相应反应
function Menu() { }
Menu.prototype.toggle = function (state) {
state();
}

//我们首先定义状态类,表示一种状态,包含其相应的处理方法
var menuStates = {
"show": function () {
console.log("the menu is showing");
},
"hide": function () {
console.log("the menu is hiding");
}
};

//这段代码实例化了一个Menu对象,然后分别切换了显示和隐藏两种状态,如果有第三种状态,我们只需要
//menuStates添加相应的状态和处理程序即可
var menu = new Menu();
menu.toggle(menuStates.show);
menu.toggle(menuStates.hide);
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
const FSM = (() => {
let currenState = "download";
return {
download: {
click: () => {
console.log("暂停下载");
currenState = "pause";
},
del: () => {
console.log("先暂停, 再删除");
}
},
pause: {
click: () => {
console.log("继续下载");
currenState = "download";
},
del: () => {
console.log("删除任务");
currenState = "deleted";
}
},
deleted: {
click: () => {
console.log("任务已删除, 请重新开始");
},
del: () => {
console.log("任务已删除");
}
},
getState: () => currenState
};
})();

class Download {
constructor(fsm) {
this.fsm = fsm;
}

handleClick() {
const { fsm } = this;
fsm[fsm.getState()].click();
}

hanldeDel() {
const { fsm } = this;
fsm[fsm.getState()].del();
}
}

// 开始下载
let download = new Download(FSM);

download.handleClick(); // 暂停下载
download.handleClick(); // 继续下载
download.hanldeDel(); // 下载中,无法执行删除操作
download.handleClick(); // 暂停下载
download.hanldeDel(); // 删除任务

策略模式(Strategy)

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。策略模式就是能够把一系列“可互换的”算法封装起来,并根据用户需求来选择其中一种。

策略模式实现的核心就是:将算法的使用和算法的实现分离。算法的实现交给策略类。算法的使用交给环境类,环境类会根据不同的情况选择合适的算法。

优缺点

  • 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
  • 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

js实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var calculateBouns = function(salary,level) {
if(level === 'A') {
return salary * 4;
}
if(level === 'B') {
return salary * 3;
}
if(level === 'C') {
return salary * 2;
}
};
// 调用如下:
console.log(calculateBouns(4000,'A')); // 16000
console.log(calculateBouns(2500,'B')); // 7500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 缓动算法: 接收4个参数,分别表示: 动画已消失的时间, 小球原始位置, 小球目标位置, 动画持续的总时间
*/
var tween = {
linear: function(t, b, c, d){
return c*t/d + b;
},
easeIn: function(t, b, c, d){
return c * ( t /= d ) * t + b;
},
strongEaseIn: function(t, b, c, d){
return c * ( t /= d ) * t * t * t * t + b;
},
strongEaseOut: function(t, b, c, d){
return c * ( ( t = t / d -1 ) * t * t * t * t + 1 ) + b;
},
sineaseIn: function(t, b, c, d){
return c * ( t /= d ) * t * t + b;
},
sineaseOut: function(t, b, c, d){
return c * ( ( t = t / d -1 ) * t * t + 1 ) +b;
}
};

模板方法模式(Template Method)

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。模板模式是抽象父类定义了子类需要重写的相关方法。 而这些方法,仍然是通过父类方法调用的。

模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

优缺点

  • 优点: 1、封装不变部分,扩展可变部分。 2、提取公共代码,便于维护。 3、行为由父类控制,子类实现。
  • 缺点:每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

js实现

ES6实现

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
class Animal {
constructor() {
// this 指向实例
this.live = () => {
this.eat();
this.sleep();
};
}

eat() {
throw new Error("模板类方法必须被重写");
}

sleep() {
throw new Error("模板类方法必须被重写");
}
}

class Dog extends Animal {
constructor(...args) {
super(...args);
}
eat() {
console.log("狗吃粮");
}
sleep() {
console.log("狗睡觉");
}
}

class Cat extends Animal {
constructor(...args) {
super(...args);
}
eat() {
console.log("猫吃粮");
}
sleep() {
console.log("猫睡觉");
}
}

/********* 以下为测试代码 ********/

// 此时, Animal中的this指向dog
let dog = new Dog();
dog.live();

// 此时, Animal中的this指向cat
let cat = new Cat();
cat.live();

TS实现

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
public abstract class Beverage{    //饮料抽象类
init():void{ //模板方法
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}

boilWater():void{ //具体方法
console.log("把水煮沸")
}

abstract brew():void //抽象方法brew
abstract addCondiments():void //抽象方法addCondiments
abstract pourInCup():void //抽象方法pourInCup
}

public class Coffee extends Beverage{ //Coffee类
brew():void{ //子类中重写brew方法
console.log("用沸水冲泡咖啡")
}
pourInCup():void{ //子类中重写pourInCup方法
console.log("把咖啡倒进杯子")
}
addCondiments():void{ //子类中重写addCondiments方法
console.log("加糖和牛奶")
}
}

public class Tea extends Beverage{ //Tea类
brew():void{ //子类中重写brew方法
console.log("用沸水浸泡茶叶")
}

pourInCup():void{ //子类中重写pourInCup方法
console.log("把茶倒进杯子")
}

addCondiments():void{ //子类中重写addCondiments方法
console.log("加柠檬")
}
}
Beverage coffee = new Coffee() // 创建coffee对象
coffee.init() // 把水煮沸、用沸水冲泡咖啡、把咖啡倒进杯子、加糖和牛奶
Beverage tea = new Tea() // 创建tea对象
tea.init() // 把水煮沸、用沸水浸泡茶叶、把茶倒进杯子、加柠檬

钩子方法
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
 class Beverage {
init(): void {
this.boilWater()
this.brew()
this.pourInCup()
if (this.customerWantsCondiments() ){ // 如果挂钩返回true,则需要调料
this.addCondiments();
}
}

boilWater(): void {
console.log("把水煮沸")
}

brew() {
throw new Error('子类必须重写brew方法')
}

pourInCup() {
throw new Error('子类必须重写pourInCup方法')
}

customerWantsCondiments ():boolean {
return true
}
addCondiments() {
throw new Error('子类必须重写addCondiments方法')
}
}

class Coffee extends Beverage {
brew() {
console.log("用沸水冲泡咖啡")
}

pourInCup(){
console.log("把咖啡倒进杯子")
}

customerWantsCondiments() {
return window.confirm( '请问需要调料吗?' );
}
addCondiments() {
console.log( '加糖和牛奶' );
}
}

Coffee coffee = new Coffee()
coffee.init()

访问者模式(Visitor)

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

优缺点

  • 优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
  • 缺点: 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
28
29
var Visitor = (function() {
return {
splice: function(){
var args = Array.prototype.splice.call(arguments, 1)
return Array.prototype.splice.apply(arguments[0], args)
},
push: function(){
var len = arguments[0].length || 0
var args = this.splice(arguments, 1)
arguments[0].length = len + arguments.length - 1
return Array.prototype.push.apply(arguments[0], args)
},
pop: function(){
return Array.prototype.pop.apply(arguments[0])
}
}
})()

var a = new Object()
console.log(a.length)
Visitor.push(a, 1, 2, 3, 4)
console.log(a.length)
Visitor.push(a, 4, 5, 6)
console.log(a.length)
Visitor.pop(a)
console.log(a)
console.log(a.length)
Visitor.splice(a, 2)
console.log(a)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 访问者模式:数组方法封装
var Visitor = (function() {
return {
splice: function() {
var args = Array.prototype.splice.call(arguments, 1);
return Array.prototype.splice.apply(arguments[0], args);
},
push: function() {
var len = arguments[0].length || 0;
var args = this.splice(arguments, 1);
arguments[0].length = len + arguments.length - 1;
return Array.prototype.push.apply(arguments[0], args);
},
pop: function() {
return Array.prototype.pop.apply(arguments[0]);
}
}
})();

var a = new Object();
Visitor.push(a,1,2,3,4);
Visitor.push(a,4,5,6);
Visitor.pop(a);
Visitor.splice(a,2);