1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>State Pattern</title>
6 </head>
7 <body>
8
9 <script>
10 /**
11 * 状态模式
12 *
13 * 定义:
14 * 允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
15 *
16 * 本质:
17 * 根据状态来分离和选择行为
18 *
19 * 1.状态和行为
20 * 所谓对象的状态,通常指的就是对象实例的属性的值;而行为指的就是对象的功能,再具体点,行为大多可以对应到方法上。
21 * 状态模式的功能就是分离状态和行为,通过维护状态的变化,来调用不同状态的的不同功能。
22 * 也就是说,状态和行为是相关联的,它们的关系可以描述为:状态决定行为。
23 *
24 * 2.行为的平行性
25 * 平行性指的是各个状态的行为所处的层次是一样的,相互是独立的,没有关联的,是根据不同的状态来决定到底走平行线哪一条。行为是不用的,当然对应的实现也是不同的,相互之间是不可替换的。
26 * 平等性强调的是可替换性,大家是同一行为的不同描述或实现,因此在同一个行为发生的时候,可以根据条件挑选任意的一个实现来进行相应的处理。
27 * 状态模式和策略模式的结构完全一样。状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,可相互替换的。
28 *
29 * 3.上下文和状态处理对象呢
30 * 在状态模式中,上下文是持有状态的对象,但是上下文自身并不处理跟状态相关的行为,而是把处理状态的功能委托给了状态对应的状态处理类来处理。
31 * 在具体的状态处理类中经常需要获取上下文自身的数据,甚至在必要的时候会回调上下文的方法,因此,通常将上下文自身当作一个参数传递给具体的状态处理类。
32 * 客户端一般只和上下文交互。客户端可以用状态对象来配置一个上下文,一旦配置完毕,就不需要再和状态对象打交道了。
33 *
34 * 4.不完美的OCP体验
35 * 由于状态的维护和转换在状态模式结构里面,不管你是扩展了状态实现类,还是新添加了状态实现类,都需要修改状态维护和转换的地方。
36 *
37 * 5.创建和销毁状态对象
38 * 究竟何时创建和销毁状态对象?
39 * 1)当需要使用状态对象的时候创建,使用完后销毁它们
40 * 2)提前创建它们并始终不销毁。
41 * 3)采用延迟加载和缓存合用的方式,就是当第一次需要使用状态对象的时候创建,使用完后并不销毁对象,而是把这个对象缓存起来,等待下一次使用,而且在合适的时候,会有缓存框剪销毁状态对象。
42 * 如果状态在运行时是不可知的,而且上下文比较稳定,建议选择1.
43 * 如果状态改变很频繁,而且状态对象还存储着大量的数据信息,建议选择2.
44 * 如果无法确定状态改变是否频繁,而且有些状态对象的状态数据量大,有些较小,建议选择3.
45 *
46 * 6.状态模式的调用顺序
47 * 在Context中进行状态的维护和转换:
48 * 1)调用上下文的方法来处理业务请求。
49 * 2)判断并维护状态。
50 * 3)根据状态来调用相应的状态处理对象的方法。
51 *
52 * 采用让状态对象来维护和转换状态的调用顺序
53 * 1)调用上下文的方法来处理业务请求。
54 * 2)获取State对象。
55 * 3)委托让相应的状态对象去处理。
56 * 4)调用构造方法得到下一个状态对象的实例。
57 * 5)设置下一个状态处理对象。
58 * 6)再到6),直到结束。
59 *
60 * 状态的维护和转换控制
61 * 所谓状态的维护,指的是维护状态的数据,给状态设置不用的状态值;而状态的转换,指的是根据状态的变化来选择不用的状态处理对象。在状态模式中,通常有两个地方可以进行状态的维护和转换控制。
62 * 一个就是在上下文中。因为状态本身通常被实现为上下文对象的状态,因此可以在上下文中进行状态维护,当然也就可以控制状态的转换了。
63 * 另外一个地方就是在状态的处理类中。当每个状态处理对象处理完自身状态所对应的功能后,可以根据需要指定后继状态,以便让应用能正确处理后继的请求。
64 *
65 * 如何选择这两种方式?
66 * 1.如果状态转换的规则是一定的,一般不需要进行什么扩展规则,那么就适合在上下文中统一进行状态的维护。
67 * 2.如果状态的转换取决于前一个状态动态处理的结果,或者是依赖于外部数据,为了增强灵活性,这种情况下,一般是在状态处理类中进行状态的维护。
68 *
69 * 还可以使用数据库来维护状态
70 *
71 */
72
73 (function () {
74 // 示例代码
75
76 // 实现一个与Context的一个特定状态相关的行为
77 function ConcreteStateA() {}
78
79 ConcreteStateA.prototype.handle = function () {};
80
81 function ConcreteStateB() {}
82
83 ConcreteStateB.prototype.handle = function () {};
84
85 // 定义客户感兴趣的接口,通常会维护一个State的对象实例
86 function Context(state) {
87 this.state = state;
88 }
89
90 Context.prototype = {
91 request: function (param) {
92 this.state.handle(param);
93 }
94 };
95 }());
96
97 (function () {
98 // 示例
99
100 function NormalVoteState() {}
101
102 NormalVoteState.prototype.vote = function (user, voteItem, voteManager) {
103 voteManager.mapVote[user] = voteItem;
104 };
105
106 function RepeatVoteState() {}
107
108 RepeatVoteState.prototype.vote = function (user, voteItem, voteManager) {
109 console.log('请不要重复投票');
110 };
111
112 function SpliteVoteState() {}
113
114 SpliteVoteState.prototype.vote = function (user, coteItem, voteManager) {
115 var s = voteManager.mapVote[user];
116 if (s) {
117 delete voteManager.mapVote[user];
118 }
119
120 console.log('你有恶意刷票行为,取消投票资格');
121 };
122
123 function BlackVoteState() {}
124
125 BlackVoteState.prototype.vote = function (user, voteItem, voteManager) {
126 console.log('进入黑名单,将禁止登录和使用本系统');
127 };
128
129 function VoteManager() {
130 this.state = null;
131 this.mapVote = {};
132 this.mapVoteCount = {};
133 }
134
135 VoteManager.prototype = {
136 vote: function (user, voteItem) {
137 var oldVoteCount = this.mapVoteCount[user] || 0;
138
139 this.mapVoteCount[user] = ++oldVoteCount;
140
141 if (oldVoteCount == 1) {
142 this.state = new NormalVoteState();
143 } else if (oldVoteCount > 1 && oldVoteCount < 5) {
144 this.state = new RepeatVoteState();
145 } else if (oldVoteCount >= 5 && oldVoteCount < 8) {
146 this.state = new SpliteVoteState();
147 } else if (oldVoteCount >= 8) {
148 this.state = new BlackVoteState();
149 }
150
151 this.state.vote(user, voteItem, this);
152 }
153 };
154
155 var vm = new VoteManager();
156 for (var i = 0; i < 8; i++) {
157 vm.vote('u1', 'A');
158 }
159
160
161 // another
162 var States = {
163 normal: function (user, voteItem, voteManager) {
164 voteManager.mapVote[user] = voteItem;
165 },
166 repeat: function (user, voteItem, voteManager) {
167 console.log('请不要重复投票');
168 },
169 splite: function (user, coteItem, voteManager) {
170 var s = voteManager.mapVote[user];
171 if (s != null) {
172 delete voteManager.mapVote[user];
173 }
174
175 console.log('你有恶意刷票行为,取消投票资格');
176 },
177 black: function (user, voteItem, voteManager) {
178 console.log('进入黑名单,将禁止登录和使用本系统');
179 }
180 };
181
182 function VoteManager2() {
183 this.state = null;
184 this.mapVote = {};
185 this.mapVoteCount = {};
186 }
187
188 VoteManager2.prototype = {
189 vote: function (user, voteItem) {
190 var oldVoteCount = this.mapVoteCount[user] || 0;
191
192 this.mapVoteCount[user] = ++oldVoteCount;
193
194 var state;
195 if (oldVoteCount == 1) {
196 state = 'normal';
197 } else if (oldVoteCount > 1 && oldVoteCount < 5) {
198 state = 'repeat';
199 } else if (oldVoteCount >= 5 && oldVoteCount < 8) {
200 state = 'splite';
201 } else if (oldVoteCount >= 8) {
202 state = 'black';
203 }
204
205 this.state = States[state];
206
207 this.state(user, voteItem, this);
208 }
209 };
210
211 var vm = new VoteManager2();
212 for (var i = 0; i < 8; i++) {
213 vm.vote('u1', 'A');
214 }
215 }());
216
217 (function () {
218 // 在状态处理类中进行后继状态的维护和转换
219
220 function NormalVoteState() {
221 this.vote = function (user, voteItem, voteManager) {
222 voteManager.mapVote[user] = voteItem;
223 console.log('恭喜你投票成功');
224 // 正常投票完毕,维护下一个状态,同一个人再投票就重复了
225 voteManager.mapState[user] = new RepeatVoteState();
226 };
227 }
228
229 function RepeatVoteState() {
230 this.vote = function (user, voteItem, voteManager) {
231 console.log('请不要重复投票');
232 if (voteManager.mapVoteCount[user] >= 4) {
233 voteManager.mapState[user] = new SpliteVoteState();
234 }
235 };
236 }
237
238 function SpliteVoteState() {
239 this.vote = function (user, voteItem, voteManager) {
240 var s = voteManager.mapVote[user];
241
242 if (s != null) {
243 delete voteManager.mapVote[user];
244 }
245
246 console.log('你有恶意刷票行为,取消投票资格');
247
248 if (voteManager.mapVoteCount[user] >= 7) {
249 voteManager.mapState[user] = new BlackVoteState();
250 }
251 };
252 }
253
254 function BlackVoteState() {
255 this.vote = function (user, voteItem, voteManager) {
256 console.log('进入黑名单,将禁止登录和使用本系统');
257 };
258 }
259
260 function VoteManager() {
261 this.mapState = {};
262 this.mapVote = {};
263 this.mapVoteCount = {};
264
265 this.vote = function (user, voteItem) {
266 var oldVoteCount = this.mapVoteCount[user];
267
268 if (oldVoteCount == null) {
269 oldVoteCount = 0;
270 }
271 this.mapVoteCount[user] = ++oldVoteCount;
272
273 var state = this.mapState[user];
274 if (state == null) {
275 state = new NormalVoteState();
276 }
277
278 state.vote(user, voteItem, this);
279 };
280 }
281
282
283 var vm = new VoteManager();
284 for (var i = 0; i < 8; i++) {
285 vm.vote('u1', 'A');
286 }
287
288 // another way
289
290 function VoteManager2() {
291 var mapState = {};
292 var mapVote = {};
293 var mapVoteCount = {};
294
295 this.vote = function (user, voteItem) {
296 var oldVoteCount = mapVoteCount[user];
297
298 if (oldVoteCount == null) {
299 oldVoteCount = 0;
300 }
301 mapVoteCount[user] = ++oldVoteCount;
302
303 var state = mapState[user];
304 if (state == null) {
305 state = voteNormal;
306 }
307
308 state(user, voteItem);
309 };
310
311 function voteNormal(user, voteItem) {
312 mapVote[user] = voteItem;
313 console.log('恭喜你投票成功');
314 // 正常投票完毕,维护下一个状态,同一个人再投票就重复了
315 return mapState[user] = voteRepeat;
316 }
317
318 function voteRepeat(user, voteItem) {
319 console.log('请不要重复投票');
320 if (mapVoteCount[user] >= 4) {
321 return mapState[user] = voteSplite;
322 }
323 }
324
325 function voteSplite(user, voteItem) {
326 var s = mapVote[user];
327
328 if (s != null) {
329 delete mapVote[user];
330 }
331
332 console.log('你有恶意刷票行为,取消投票资格');
333
334 if (mapVoteCount[user] >= 7) {
335 return mapState[user] = voteBlack;
336 }
337 }
338
339 function voteBlack(user, voteItem) {
340 console.log('进入黑名单,将禁止登录和使用本系统');
341 }
342 }
343
344 var vm = new VoteManager2();
345 for (var i = 0; i < 8; i++) {
346 vm.vote('u1', 'A');
347 }
348 }());
349
350 (function () {
351 // 模拟工作流
352 /*
353 请假流程,需项目经理和部门经理审批
354 */
355
356 // 公共状态处理机
357 function LeaveRequestContext() {
358 // 持有一个状态对象
359 this.state = null;
360 // 包含流程处理需要的业务数据对象
361 this.businessVO = null;
362 }
363
364 LeaveRequestContext.prototype = {
365 // 执行工作
366 doWork: function () {
367 this.state.doWork(this);
368 }
369 };
370
371 // 定义请假单的业务数据模型
372 function LeaveRequestModel() {
373 // 请假人
374 this.user = '';
375 // 请假开始时间
376 this.beginDate = '';
377 // 请假天数
378 this.leaveDays = '';
379 // 审核结果
380 this.result = '';
381 }
382
383 function ProjectManagerState() {
384 this.doWork = function (request) {
385 var leaveRequestModel = request.businessVO;
386
387 console.log('项目经理审核中,请稍候。。');
388 console.log(leaveRequestModel.user + '申请从'
389 + leaveRequestModel.beginDate + '开始请假'
390 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)');
391
392 var answer = window.prompt('1为同意,2为不同意');
393 var result = answer == 1 ? '同意' : '不同意';
394 leaveRequestModel.result = '项目经理审核结果:' + result;
395
396 if (answer == 1) {
397 if (leaveRequestModel.leaveDays > 3) {
398 request.state = new DepManagerState();
399 } else {
400 request.state = new AuditOverState();
401 }
402 } else {
403 request.state = new AuditOverState();
404 }
405
406 request.doWork();
407 };
408 }
409
410 function DepManagerState() {
411 this.doWork = function (request) {
412 var leaveRequestModel = request.businessVO;
413
414 console.log('部门经理审核中,请稍候。。');
415 console.log(leaveRequestModel.user + '申请从'
416 + leaveRequestModel.beginDate + '开始请假'
417 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)');
418
419 var answer = window.prompt('1为同意,2为不同意');
420 var result = answer == 1 ? '同意' : '不同意';
421 leaveRequestModel.result = '部门经理审核结果:' + result;
422
423 request.state = new AuditOverState();
424
425 request.doWork();
426 };
427 }
428
429 function AuditOverState() {
430 this.doWork = function (request) {
431 var leaveRequestModel = request.businessVO;
432 // do sth
433 console.log(leaveRequestModel.user + ',你的请假申请已经审核结束,结果是:' + leaveRequestModel.result);
434 };
435 }
436
437 var lrm = new LeaveRequestModel();
438 lrm.user = '小林';
439 lrm.beginDate = '2014-4-2';
440 lrm.leaveDays = 5;
441
442 var request = new LeaveRequestContext();
443 request.businessVO = lrm;
444 request.state = new ProjectManagerState();
445
446 request.doWork();
447
448
449 // another
450
451 function LeaveRequestContext2() {
452 this.state = null;
453 // 包含流程处理需要的业务数据对象
454 this.businessVO = null;
455 this.doWork = function () {
456 if (typeof this.state == 'function') {
457 this.state = this.state(this);
458 this.doWork();
459 }
460 };
461 }
462
463 function projectManagerState(request) {
464 var leaveRequestModel = request.businessVO;
465
466 console.log('项目经理审核中,请稍候。。');
467 console.log(leaveRequestModel.user + '申请从'
468 + leaveRequestModel.beginDate + '开始请假'
469 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)');
470
471 var answer = window.prompt('1为同意,2为不同意');
472 var result = answer == 1 ? '同意' : '不同意';
473 leaveRequestModel.result = '项目经理审核结果:' + result;
474
475 var state;
476 if (answer == 1) {
477 if (leaveRequestModel.leaveDays > 3) {
478 state = depManagerState;
479 } else {
480 state = auditOverState;
481 }
482 } else {
483 state = auditOverState;
484 }
485
486 return state;
487 }
488
489 function depManagerState(request) {
490 var leaveRequestModel = request.businessVO;
491
492 console.log('部门经理审核中,请稍候。。');
493 console.log(leaveRequestModel.user + '申请从'
494 + leaveRequestModel.beginDate + '开始请假'
495 + leaveRequestModel.leaveDays + '天,请项目经理审核(1为同意,2为不同意)');
496
497 var answer = window.prompt('1为同意,2为不同意');
498 var result = answer == 1 ? '同意' : '不同意';
499 leaveRequestModel.result = '部门经理审核结果:' + result;
500
501 return auditOverState;
502 }
503
504 function auditOverState(request) {
505 var leaveRequestModel = request.businessVO;
506 // do sth
507 console.log(leaveRequestModel.user + ',你的请假申请已经审核结束,结果是:' + leaveRequestModel.result);
508 }
509
510 var lrm = new LeaveRequestModel();
511 lrm.user = '小林';
512 lrm.beginDate = '2014-4-2';
513 lrm.leaveDays = 5;
514
515 var request = new LeaveRequestContext2();
516 request.businessVO = lrm;
517 request.state = projectManagerState;
518
519 request.doWork();
520
521 }());
522
523 /*
524 何时选用状态模式
525 1.如果一个对象的行为取决于它的状态,而且它必须在运行时刻根据状态来改变它的行为,可以使用状态模式把状态和行为分离。
526 2.如果一个操作中含有庞大的多分支语句,而且这些分支依赖于该对象的状态,可以使用状态模式,把各个分支的处理分散包装到单独的对象处理类中。
527
528
529 相关模式
530
531 状态模式和策略模式
532 两个结构相同。状态模式的行为是平行性的,不可相互替换的;而策略模式的行为是平等性的,可相互替换的。
533
534 状态模式和观察者模式
535 相似但又有区别,可以组合使用。
536 这两个模式都是在状态发生改变的时候触发行为,只不过观察者模式的行为是固定的,那就是通知所有观察者;而状态模式是根据状态来选择不同的处理。
537 从表面来看,两个模式相似,观察者模式中的被观察对象就好比状态模式中的上下文,观察者模式中当被观察对象的状态发生改变的时候,触发的通知所有观察者的方法就好比状态模式中,根据状态的变化选择对应的状态处理。
538 但实际这两个模式是不同的,观察者模式的目的是在被观察者的状态发生改变的时候,触发观察联动,具体如何处理观察者模式不管;而状态模式的主要目的在于根据状态来分离和选择行为,当状态发生改变的时候,动态地改变行为。
539 这两个模式可以组合使用,比如在观察者模式的观察者部分,当被观察对象的状态发生改变,触发通知了所有的观察者后,使用状态模式根据通知过来的状态选择相应的处理。
540
541 状态模式和单例模式
542 可以组合使用
543 把状态模式中的状态处理类实现成单例。
544
545 状态模式和享元模式
546 可以组合使用
547 由于状态模式把状态对应的行为分散到多个状态对象中,会造成很多细粒度的状态对象,可以把这些状态处理对象通过享元模式来共享,从而节省资源。
548 */
549
550 (function () {
551 // http://www.dofactory.com/javascript-state-pattern.aspx
552 var TrafficLight = function () {
553
554 var count = 0;
555 var currentState = new Red(this);
556
557 this.change = function (state) {
558 // limits number of changes
559 if (count++ >= 10) return;
560
561 currentState = state;
562 currentState.go();
563 };
564
565 this.start = function () {
566 currentState.go();
567 };
568 }
569
570 var Red = function (light) {
571 this.light = light;
572
573 this.go = function () {
574 log.add("Red --> for 1 minute");
575 light.change(new Green(light));
576 }
577 };
578
579 var Yellow = function (light) {
580 this.light = light;
581
582 this.go = function () {
583 log.add("Yellow --> for 10 seconds");
584 light.change(new Red(light));
585 }
586 };
587
588 var Green = function (light) {
589 this.light = light;
590
591 this.go = function () {
592 log.add("Green --> for 1 minute");
593 light.change(new Yellow(light));
594 }
595 };
596
597 // log helper
598
599 var log = (function () {
600 var log = "";
601 return {
602 add: function (msg) { log += msg + "\n"; },
603 show: function () {
604 alert(log);
605 log = "";
606 }
607 }
608 })();
609
610 function run() {
611
612 var light = new TrafficLight();
613 light.start();
614
615 log.show();
616 }
617 }());
618 </script>
619 </body>
620 </html>