【笔记】设计模式及js实现(三)行为型模式
Mar 30, 2019前端js设计模式设计模式及js实现(三)行为型模式
23种设计模式根据其目的(模式是用来做什么的)可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三种:
- 创建型模式主要用于创建对象。
- 结构型模式主要用于处理类或对象的组合。
- 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责。
目录
行为型模式
- 职责链模式(Chain of Responsibility)
- 命令模式(Command)
- 解释器模式(Interpreter)
- 迭代器模式(Iterator)
- 中介者模式(Mediator)
- 备忘录模式(Memento)
- 观察者模式(Observer)
- 状态模式(State)
- 策略模式(Strategy)
- 模板方法模式(Template Method)
- 访问者模式(Visitor)
行为型模式
职责链模式(Chain of Responsibility)
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。责任链模式:多个对象均有机会处理请求,从而解除发送者和接受者之间的耦合关系。这些对象连接成为链式结构,每个节点转发请求,直到有对象处理请求为止。
优缺点
- 优点: 1、降低耦合度。它将请求的发送者和接收者解耦。 2、简化了对象。使得对象不需要知道链的结构。3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。 4、增加新的请求处理类很方便。
- 缺点: 1、不能保证请求一定被接收。 2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。 3、可能不容易观察运行时的特征,有碍于除错。
js实现
1 | var MoneyStack = function(billSize) { |
1 | var ATM = function() { |
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
66class 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 |
|
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
53class 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 | // IE 上传控件 |
ES6实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18const 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 | var Participant = function(name) { |
1 |
|
备忘录模式(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 | const event = { |
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 | //我们首先定义一个环境类,在这里也就是菜单对象,拥有一个状态成员,可以修改其状态并作出相应反应 |
1 | const FSM = (() => { |
策略模式(Strategy)
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。策略模式就是能够把一系列“可互换的”算法封装起来,并根据用户需求来选择其中一种。
策略模式实现的核心就是:将算法的使用和算法的实现分离。算法的实现交给策略类。算法的使用交给环境类,环境类会根据不同的情况选择合适的算法。
优缺点
- 优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
- 缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
js实现
1 | var calculateBouns = function(salary,level) { |
1 | /* |
模板方法模式(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
51class 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
46public 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 | class Beverage { |
访问者模式(Visitor)
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
优缺点
- 优点: 1、符合单一职责原则。 2、优秀的扩展性。 3、灵活性。
- 缺点: 1、具体元素对访问者公布细节,违反了迪米特原则。 2、具体元素变更比较困难。 3、违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
js实现
1 | var Visitor = (function() { |
1 | // 访问者模式:数组方法封装 |
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com