设计模式
Star Sea

单例模式

惰性单例模式封装

1
2
3
4
5
6
var getSingle = function (fn) {
var result;
return function () {
return result || ( result = fn.apply(this, arguments) );
}
}

策略模式

定义:定义一系列的算法,把他们一个个封装起来,并使他们可以相互替换。
除了算法外,策略模式还可以用来封装一系列的业务规则,例如:表单校验,业务规则可以对应表单校验的规则和错误的提示语。定义好规则策略后,需要校验的地方传入规则名和错误提示即可。

代理模式

定义:代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

保护代理:可以增加条件做过滤,拒绝掉一部分的请求,控制不同权限的对象对目标对象的访问
虚拟代理:对耗时或者可合并的操作等,做延迟处理,在合适的时机触发。例如:加载图片、合并接口请求等。

合并请求的虚拟代理例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var synchromousFile = function ( id ) {
console.log('开始同步文件,id为' + id);
};

var proxySynchronousFile = (function () {
var cache = [], // 保存一段时间内需要同步的ID集合
timer;

return function( id ) {
cache.push(id);
if ( timer ) {
return;
}

timer = setTimeout(function() {
synchromousFile( cache.join(',')); // 2秒后向本体发送需要同步的ID集合
clearTimeout( timer );
timer = null;
cache.length = 0; //清空ID集合
}, 2000)
}
})();

发布-订阅模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知。
优点:一为时间上的解耦,二为对象之间的解耦。
缺点:过多的使用会使得模块之间的联系被隐藏到了背后。

以下为全局的发布-订阅对象:

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
var Event = (function () {

var clientList = {},
listen,
trigger,
remove;

listen = function (key, fn) {
if (!clientList[ key ]) {
clientList[ key ] = [];
}
clientList[ key ].push( fn );
};

trigger = function () {
var key = Array.prototype.shift.call( arguments ),
fns = clientList[ key ];
if ( !fns || fns.length === 0) {
return false;
}
for ( var i = 0, fn; fn = fns[ i++ ]; ) {
fn.apply( this, arguments);
}
}

remove = function ( key, fn ) {
var fns = clientList[ key ];
if ( !fns ) {
return false;
}
if ( !fn ) {
fns && ( fns.length = 0);
} else {
for ( var l = fns.length - 1; l >= 0; l--) {
var _fn = fns[ l ];
if ( _fn === fn ) {
fns.splice( l, 1);
}
}
}
};

return {
listen: listen,
trigger: trigger,
remove: remove
}
})();

命令模式

定义:指的是一个执行某些特定事情的指令。
最常用的场景:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系。
命令模式支持撤销、排队、宏命令等。

使用闭包的命令模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var setCommand = function ( button, func ) {
button.onclick = function () {
func();
}
};

var MenuBar = {
refresh: function () {
console.log( '刷新' );
}
};

var RefreshMenuBarCommand = function ( receiver ) {
return function () {
receiver.refresh();
}
};

var refreshMenuBarCommand = RefreshMenuBarCommand( MenuBar );

setCommand( button1, refreshMenuBarCommand);

组合模式

组合模式将对象组合成树形结构,以表示“部分-整体”的层次结构。除了用来表示树形结构之外,组合模式的另一个好处是通过对象的多态性表现,使得用户对单个对象和组合对象的使用具有一致性,下面分别说明。

  • 表示树形结构。通过回顾上面的例子,我们很容易找到组合模式的一个优点:提供了一种遍历树形结构的方案,通过调用组合对象的execute方法,程序会递归调用组合对象下面的叶对象的execute方法,所以我们的万能遥控器只需要一次操作,便能依次完成关 门、打开电脑、登录QQ这几件事情。组合模式可以非常方便地描述对象部分-整体层次结构。
  • 利用对象多态性统一对待组合对象和单个对象。利用对象的多态性表现,可以使客户端忽略组合对象和单个对象的不同。在组合模式中,客户将统一地使用组合结构中的所有对象,而不需要关心它究竟是组合对象还是单个对象。
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
// 创建一个宏命令
var MacroCommand = function(){
return {
// 宏命令的子命令列表
commandsList: [],
// 添加命令到子命令列表
add: function( command ){
this.commandsList.push( command );
},
// 依次执行子命令列表里面的命令
execute: function(){
for ( var i = 0, command; command = this.commandsList[ i++ ]; ){
command.execute();
}
}
}
};

<!--打开空调命令-->
var openAcCommand = {
execute: function(){
console.log( '打开空调' );
}
};

<!--打开电视和音响-->
var openTvCommand = {
execute: function(){
console.log( '打开电视' );
}
};
var openSoundCommand = {
execute: function(){
console.log( '打开音响' );
}
};
//创建一个宏命令
var macroCommand1 = MacroCommand();
//把打开电视装进这个宏命令里
macroCommand1.add(openTvCommand)
//把打开音响装进这个宏命令里
macroCommand1.add(openSoundCommand)

<!--关门、打开电脑和打登录QQ的命令-->
var closeDoorCommand = {
execute: function(){
console.log( '关门' );
}
};
var openPcCommand = {
execute: function(){
console.log( '开电脑' );
}
};
var openQQCommand = {
execute: function(){
console.log( '登录QQ' );
}
};
//创建一个宏命令
var macroCommand2 = MacroCommand();
//把关门命令装进这个宏命令里
macroCommand2.add( closeDoorCommand );
//把开电脑命令装进这个宏命令里
macroCommand2.add( openPcCommand );
//把登录QQ命令装进这个宏命令里
macroCommand2.add( openQQCommand );

<!--把各宏命令装进一个超级命令中去-->
var macroCommand = MacroCommand();
macroCommand.add( openAcCommand );
macroCommand.add( macroCommand1 );
macroCommand.add( macroCommand2 );

享元模式

享元模式的核心是运用共享技术来有效支持大量细粒度的对象,如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用。

享元模式要求对象的属性划分为内部状态和外部状态,如何划分:

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会变化
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

示例:文件上传

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
var Upload = function(uploadType) {
this.uploadType = uploadType;
}

/* 删除文件(内部状态) */
Upload.prototype.delFile = function(id) {
uploadManger.setExternalState(id, this); // 把当前id对应的外部状态都组装到共享对象中
// 大于3000k提示
if(this.fileSize < 3000) {
return this.dom.parentNode.removeChild(this.dom);
}
if(window.confirm("确定要删除文件吗?" + this.fileName)) {
return this.dom.parentNode.removeChild(this.dom);
}
}

/** 工厂对象实例化
* 如果某种内部状态的共享对象已经被创建过,那么直接返回这个对象
* 否则,创建一个新的对象
*/
var UploadFactory = (function() {
var createdFlyWeightObjs = {};
return {
create: function(uploadType) {
if(createdFlyWeightObjs[ uploadType ]) {
return createdFlyWeightObjs[ uploadType ]
}
return createdFlyWeightObjs[ uploadType ] = new Upload( uploadType );
}
}
})();

/* 管理器封装外部状态 */
var uploadManger = (function() {
var uploadDatabase = {};

return {
add: function(id, uploadType, fileName, fileSize) {
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = "<span>文件名称:" + fileName + ",文件大小:" + fileSize +"</span>"
+ "<button class='delFile'>删除</button>";

dom.querySelector(".delFile").onclick = function() {
flyWeightObj.delFile(id);
};
document.body.appendChild(dom);

uploadDatabase[id] = {
fileName: fileName,
fileSize: fileSize,
dom: dom
};

return flyWeightObj;
},
setExternalState: function(id, flyWeightObj) {
var uploadData = uploadDatabase[id];
for(var i in uploadData) {
flyWeightObj[i] = uploadData[i];
}
}
};
})();

/*触发上传动作*/
var id = 0;
window.startUpload = function(uploadType, files) {
for(var i=0,file; file = files[i++];) {
var uploadObj = uploadManger.add(++id, uploadType, file.fileName, file.fileSize);
}
};

/* 测试 */
startUpload("plugin", [
{
fileName: '1.txt',
fileSize: 1000
},{
fileName: '2.txt',
fileSize: 3000
},{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload("flash", [
{
fileName: '4.txt',
fileSize: 1000
},{
fileName: '5.txt',
fileSize: 3000
},{
fileName: '6.txt',
fileSize: 5000
}
]);

职责链模式

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

例子:异步职责链的实现

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
// Chain.prototype.setNextSuccessor 指定在链中的下一个节点
// Chain.prototype.passRequest 传递请求给某个节点
var Chain = function ( fn ) {
this.fn = fn;
this.successor = null;
}
Chain.prototype.setNextSuccessor = function ( successor ) {
return this.successor = successor;
}

// 对于ret的判断可以用于同步的职责链模式
Chain.prototype.passRequest = function () {
var ret = this.fn.apply( this, arguments);
if ( ret === 'nextSuccessor' ) {
return this.successor && this.successor.passRequest.apply( this.successor, arguments );
}
return ret;
}

// 异步时ret的return值没有,此时无法接收,因此采用next回调的方式
Chain.prototype.next = function () {
return this.nextSuccessor && this.successor.passRequest.apply( this.successor, arguments );
}

var fn1 = new Chain(function(){
console.log( 1 );
return 'nextSuccessor';
});
var fn2 = new Chain(function(){
console.log( 2 );
var self = this;
setTimeout(function(){
self.next();
}, 1000 );
});
var fn3 = new Chain(function(){
console.log( 3 );
});
fn1.setNextSuccessor( fn2 ).setNextSuccessor( fn3 );
fn1.passRequest();

用AOP实现职责链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Function.prototype.after = function( fn ){
var self = this;
return function(){
var ret = self.apply( this, arguments );
if ( ret === 'nextSuccessor' ){
return fn.apply( this, arguments );
}
return ret;
}
};

// 此处的order500yuan、order200yuan、orderNormal为订单处理的函数,此处未列出
var order = order500yuan.after( order200yuan ).after( orderNormal );
order( 1, true, 500 ); // 输出:500元定金预购,得到100优惠券
order( 2, true, 500 ); // 输出:200元定金预购,得到50优惠券
order( 1, false, 500 ); // 输出:普通购买,无优惠券

中介者模式

中介者模式的作用就是解除对象与对象之间的紧耦合关系。增加一个中介者对象后,所有的相关对象都通过中介者对象来通信,而不是互相引用,所以当一个对象发生改变时,只需要通知中介者对象即可。中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。

例子:游戏中使用中介者控制游戏状态

玩家对象的生成:

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 Player (name, teamColor) {
this.name = name;
this.teamColor = teamColor;
this.state = 'alive';
};

Player.prototype.win = function () {
console.log(`${this.name} won!`);
};

Player.prototype.lose = function () {
console.log(`${this.name} lost!`);
};
// 玩家死亡
Player.prototype.die = function () {
this.state = 'dead';
// 给中介者发送消息,玩家死亡
playerDirector.ReceiveMessage( 'playDead', this );
}

// 移除玩家
Player.prototype.remove = function () {
// 给中介者发送消息,玩家移除
playerDirector.ReceiveMessage( 'removePlayer', this );
}

// 玩家换队
Player.prototype.remove = function ( color ) {
// 给中介者发送消息,玩家换队
playerDirector.ReceiveMessage( 'changeTeam', this, color );
}

// 生成玩家的工厂函数
var palyerFactory = function ( name, teamColor ) {
var newPlayer = new Player( name, teamColor );

// 给中介者发动消息,新增玩家
playerDirecotor.ReceiveMessage( 'addPlayer', newPlayer );
return newPlayer;
}

中介者playerDirector的实现:

  • 利用发布—订阅模式。将playerDirector实现为订阅者,各player作为发布者,一旦player的状态发生改变,便推送消息给playerDirector,playerDirector处理消息后将反馈发送给其他player。
  • 在playerDirector中开放一些接收消息的接口,各player可以直接调用该接口来给playerDirector发送消息,player只需传递一个参数给playerDirector,这个参数的目的是使playerDirector可以识别发送者。同样,playerDirector接收到消息之后会将处理结果反馈给其他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
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
var playerDirector = ( function () {
var players = {}, // 保存所有玩家
operations = {}; // 中介者可以执行的操作

// 新增一个玩家
operations.addPlayer = function ( player ) {
var teamColor = player.teanmColor;
players[ teamColor ] = players[ teamColor ] || [];
players[ teamColor ].push( player );
}

// 移除一个玩家
operations.removePlayer = function ( player ) {
var teamColor = player.teamColor,
teamPlayers = players[ teamColor ] || []
for ( var i = teamPlayers.length - 1; i >= 0; i-- ){ // 遍历删除
if ( teamPlayers[ i ] === player ){
teamPlayers.splice( i, 1 );
}
}
}

// 玩家换队
operations.changeTeam = function ( player, newTeamColor ) {
operations.removePlayer( player );
player.teamColor = newTeamColor;
operations.addPlayer( player );
}

// 玩家死亡
operations.playerDead = function( player ){ // 玩家死亡
var teamColor = player.teamColor,
teamPlayers = players[ teamColor ]; // 玩家所在队伍

var all_dead = true;

for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
if ( player.state !== 'dead' ){
all_dead = false;
break;
}
}

if ( all_dead === true ){ // 全部死亡

for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
player.lose(); // 本队所有玩家lose
}

for ( var color in players ){
if ( color !== teamColor ){
var teamPlayers = players[ color ]; // 其他队伍的玩家
for ( var i = 0, player; player = teamPlayers[ i++ ]; ){
player.win(); // 其他队伍所有玩家win
}
}
}
}
};

// 开发接口
var ReceiveMessage = function () {
// 第一个参数为消息的类型
var message = Array.prototype.shift.call( arguments );
operations[ message ].apply( this, arguments );
}

return {
ReceiveMessage: ReceiveMessage
}
})();

装饰者模式

装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责。

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
var a = function(){
alert (1);
}

var _a = a;

a = function(){
_a();
alert (2);
}

a();

用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
29
30
31
Function.prototype.before = function ( beforeFn ) {
var _self = this; // 保存原函数的引用
return function () { // 返回包含了原函数和新函数的“代理”函数
beforeFn.apply( this, arguments ); // 执行新函数,保证this不被劫持

return _self.apply( this, arguments ); // 执行原函数并返回原函数的结果,保证this不被劫持
}

}
Function.prototype.after = function( afterfn ){
var _self = this;
return function(){
var ret = _self.apply( this, arguments );
afterfn.apply( this, arguments );
return ret;
}
}

// 使用
window.onload = function(){
alert (1);
}

window.onload = ( window.onload || function(){} ).after(function(){
alert (2);
}).after(function(){
alert (3);
}).after(function(){
alert (4);
});

装饰者模式和代理模式

代理模式和装饰者模式最重要的区别在于它们的意图和设计目的。代理模式的目的是,当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者。本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情。装饰者模式的作用就是为对象动态加入行为。换句话说,代理模式强调一种关系(Proxy与它的实体之间的关系),这种关系可以静态的表达,也就是说,这种关系在一开始就可以被确定。而装饰者模式用于一开始不能确定对象的全部功能时。代理模式通常只有一层代理-本体的引用,而装饰者模式经常会形成一条长长的装饰链。

状态模式

定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
我们以逗号分割,把这句话分为两部分来看。第一部分的意思是将状态封装成独立的类,并将请求委托给当前的状态对象,当对象的内部状态改变时,会带来不同的行为变化。
第二部分是从客户的角度来看,我们使用的对象,在不同的状态下具有截然不同的行为,这个对象看起来是从不同的类中实例化而来的,实际上这是使用了委托的效果。

状态模式的通用结构

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 Light = function(){
// 实例化每一个状态对象
this.offLightState = new OffLightState( this ); // 持有状态对象的引用
this.weakLightState = new WeakLightState( this );
this.strongLightState = new StrongLightState( this );
this.superStrongLightState = new SuperStrongLightState( this );
this.button = null;
};
Light.prototype.init = function () {
var button = document.createElement( 'button' ),
self = this;
this.button = document.body.appendChild( button );
this.button.innerHTML = '开关';
this.currState = this.offLightState; // 设置默认状态

this.button.onclick = function () {
self.currState.buttonWasPressed();
}
}

// 列举一个状态对象,关灯状态对象
var OfflightState = function( light ) {
this.light = light;
}

OfflightState.prototypr.buttonWasPressed = function () {
console.log('弱光');
this.light.setState( this.light.weakLightState );
}

状态机:使用闭包实现将客户的操作委托给状态对象,此委托需要处理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
var delegate = function ( client, delegation ) {
return {
buttonWasPressed: function () {
return delegation.buttonWasPressed.apply( client, arguments);
}
}
};

var FSM = {
off: {
buttonWasPressed: function(){
console.log( '关灯' );
this.button.innerHTML = '下一次按我是开灯';
this.currState = this.onState;
}
},
on: {
buttonWasPressed: function(){
console.log( '开灯' );
this.button.innerHTML = '下一次按我是关灯';
this.currState = this.offState;
}
}
};

var Light = function () {
this.offState = delegation( this, FSM.off );
this.onState = delegation( this, FSM.on );
this.currState = this.offState;
this.button = null;
}

Light.prototype.init = function () {
var button = document.createElement( 'button' );
button.innerHTML = '已关灯';
this.button = document.body.appendChild( button );
this.button.onclick = function () {
self.currState.buttonWasPressed();
}
}

var light = new Light();
light.init();

适配器模式

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。
实际项目中封装组件时,经常遇到组件定义的接口和后台返回的数据无法完全对应上,这时一个apater函数转化数据是比较方便的选择。