状态模式是一种行为软件设计模式.一个对象在其内部状态改变时会改变它的行为.各种状态和状态改变行为的操作是被独立封装的..

一般情况是:如果一个事物有多钟状态,我们用一个变量保存当前状态名(string),通过if-else 做状态的判断,然后进行状态的变更.

这样最大的问题是: 你需要将所有的状态和变更状态后的行为都放在一个方法里.这个方法会变得很臃肿和复杂.会变的难以阅读和维护.

状态模式:将各种状态和状态之间切换的行为封装在一起.只需要一个Context来触发状态变化,而状态变化后引起的行为操作Context不用处理.

状态模式的优点:

  1. 状态和行为关系封装在一起,增加新的状态和状态转换会很容易
  2. 对象代替字符串保存当前状态,状态的可切换一目了然
  3. 避免了Context无限膨胀
  4. Context中的请求动作和状态类中封装的行为非常容易,它们独立变化而不互相影响

状态模式的缺陷:

  1. 逻辑分散在状态类中,虽然避免的条件分支但也导致了逻辑分散问题
  2. 新增了封装状态和行为变化的对象,增加了代码量.

例子

简版电灯开关例子

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
var Light = function () {
this.currState = FSM.off;
this.button = null;
}
Light.prototype.init = function () {
var button = document.createElement('button');
var self = this;

button.innerHTML = '已关灯';
this.button = document.body.appendChild('button');
this.button.onclick = function () {
self.currState.buttonWasPressed.call(self); // 把请求委托给FSM状态机
}
};

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

var light = new Light();
light.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
var delegate = function (client, delegation) {
return {
buttonWasPressed () {
return delegation.buttonWasPressed.apply(client, arguments);
}
}
}

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

var Light = function () {
this.offState =delegate(this, FSM.off);
this.onState = delegate(this, FSM.on);
this.currState = this.offState; // 设置初始状态为关闭
this.button = null;
}
Light.prototype.init = function () {
var button = document.createElement('button'),
self = this;
button.innerHTML = '已关灯';
this.button = document.body.appendChild(button);
this.button.onclick = function () {
self.currState.buttonWasPressed()
}
};

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

状态模式和策略模式

状态模式和策略模式看起来很像.都有一个 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
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
window.external.upload = function (state) {
console.log(state);
}

var plugin =(function () {
var plugin = document.createElement('embed');
plugin.style.display = 'none';
plugin.type = 'application/txftn-webkit';
plugin.sign = function () {
console.log('开始文件扫描');
}

plugin.pause = function () {
console.log('暂停文件上传');
}

plugin.uploading = function () {
console.log('开始文件上传');
}

plugin.done = function () {
console.log('文件上传完成');
}

document.body.appendChild(plugin);

return plugin;
})()

// 为每种状态创建一个实例对象
var Upload = function (fileName) {
this.plugin = plugin;
this.fileName = fileName;
this.buttion1 = null;
this.button2 = null;
this.signState = new SignState(this);
this.uploadingState = new UploadingState(this);
this.pauseState = new PauseState(this);
this.doneState = new DoneState(this);
this.errorState = new ErrorState(this);
this.currState = this.signState; // 设置当前状态
}

// 绑定页面中DOM节点
Upload.prototype.init = function () {
var that = this;

this.dom = document.createElement('div');
this.dom = innerHTML = `
<span>文件名称: ${this.fileName}</span>
<button data-action="button1">扫描中</button>
<button data-action="button2">删除</button>
`

document.body.appendChild(this.dom);

this.button1 = this.dom.querySelector('[data-action="button1"]')
this.button2 = this.dom.querySelector('[data-action="button2"]')
this.bindEvent();
}

// 将按钮点击事件委托给当前状态执行
Upload.prototype.bindEvent = function () {
var self = this;
this.button1.onclick = function () {
self.currState.clickHandler1();
}
this.button2.onclick = function () {
self.currState.clickHandler2();
}
}

Upload.prototype.sign = function () {
this.plugin.sign();
this.currState = this.signState;
}

Upload.prototype.uploading = function () {
this.button1.innerHTML = '正在上传,点击暂停';
this.plugin.uploading();
this.currState = this.uploadingState;
}

Upload.prototype.pause = function () {
this.button1.innerHTML = '已暂停, 点击继续上传';
this.plugin.pause();
this.currState = this.pauseState;
}

Upload.prototype.done = function () {
this.button1.innerHTML = '上传完成';
this.plugin.done();
this.currState = this.doneState;
}

Upload.prototype.error = function () {
this.button1.innerHTML = '上传失败';
this.currState = this.errorState;
}

Upload.prototype.del = function () {
this.plugin.del();
this.dom.parentNode.removeChild(this.dom);
}

// 编写各种状态类的实现
var StateFactory = (function () {
var State = function (){};

State.prototype.clickHandler1 = function () {
throw new Error('子类必须重写父类的 clickHandler1方法');
}
State.prototype.clickHandler2 = function () {
throw new Error('子类必须重写父类的 clickHandler2方法');
}

return function (param) {
var F = function (uploadObj) {
this.uploadObj = uploadObj;
}
F.prototype = new State();

for (var i in param) {
F.prototype[i] = param[i]
}
return F;
}
})()

// 扫描中状态
var SignState = StateFactory({
clickHandler1 () {
console.log('扫描中,点击无效……');
},
clickHandler2 () {
console.log('文件正在扫描中, 不能删除');
}
})

// 上传中状态
var UploadingState = StateFactory({
clickHandler1 () {
this.uploadObj.pause();
},
clickHandler2 () {
console.log('文件正在上传中, 不能删除');
}
})

// 暂停状态
var PauseState = StateFactory({
clickHandler1 () {
this.uploadObj.uploading();
},
clickHandler2 () {
this.uploadObj.del();
}
})

// 上传完成
var DoneState = StateFactory({
clickHandler1 () {
console.log('文件上传完成,点击无效');
},
clickHandler2 () {
this.uploadObj.del();
}
})

var ErrorState = StateFactory({
clickHandler1 () {
console.log('文件上传失败,点击无效');
},
clickHandler2 () {
this.uploadObj.del();
}
})

// 测试
var uploadObj = new Upload('JavaScript 设计模式与开发实践');
uploadObj.init();
window.external.upload = function (state) {
uploadObj[state]();
}
window.external.upload('sign');

setTimeout(()=> {
window.external.upload('uploading');
}, 1000)

setTimeout(() => {
window.external.upload('done');
})