javascript设计模式--状态模式(State)

  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>

 

posted @ 2014-04-02 23:29  LukeLin  阅读(1822)  评论(0编辑  收藏  举报