js中State模式的解析及运用
状态模式,在大的范畴中的定义为当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。每种编程语言有不同的实现方式,运用的范围也多用于游戏之中。
这里我用javascript来模拟状态模式,主要思想是通过事件监听的效果来控制一个主要对象的状态。那么何为事件监听的效果呢?在其他语言中,可以通过多个对象的协作来完成状态模式,而我在javascript中可以通过对DOM节点进行事件操控来传递消息,使主要对象接收消息(当然不同状态接收到的消息所产生的反应是不一样的)。而在javascript中模拟状态模式的首要步骤,就是选出这个主要对象,并且对其他对象进行DOM控制,这听起来有些抽象,下面会有一个大致的状态转移图来让大家理解理解。
我们来详细地介绍下上图的相关对象及状态:
1.Leader : 这是这个状态模式例子中的主要对象,通过其他对象对他发送的消息来进行一些状态上的变化。
2.Player : 这是一个发送消息的对象,我们在下文把他称之为简单对象P,通过它来发送消息给主要对象。
3.Reporter : 这是一个发送消息的对象,我们在下文把他称之为简单对象R,通过它来发送小心给主要对象。
图中有一些箭头,代表的是消息发送,而矩形框里面代表着leader的状态。
可以看到当简单对象P向主要对象Leader发送一个PlayerWaveHands的消息时,Leader状态来到Leader_WaveHandsState。然后超时后返回原状态。
这里要说一点的是上图中的超时等会我们都会用一个点击事件给替代。
接着进入代码部分,没错,类与状态之间的关系确实有点难以下手。但是要理清楚的是"谁"收到了什么"消息",在条件允许的情况下,转换到另一个"状态",从而获取新状态下的方法。
"谁":指的是主要对象,也就是Leader。这个Leader需要能够接受消息,因此可以有一个能接受并反馈消息的方法,最重要的是这个消息必须在某种状态下才能对主要对象产生有效的影响,即状态转移。所以可以看出,Leader类需要有处理消息的外部接口,同时这个接口要保证消息与状态的对应,因此,Leader还需要有一个状态指针,指向某一个状态。
"消息":指的是其他简单对象向主要对象发送的消息,这个消息可以通过事件点击触发,上文也提到过,这里的简单对象用DOM实现。因此简单对象不需要有一个具体的类,这是鉴于js与html dom之间的特殊关系。不同于C++等其他语言。
"在条件允许的情况下":指的是对应的状态才能接受对应的消息,显然这里就必须要有一个基本状态类State类(里面有公共方法),和继承于这个State类的其他状态类。
"另一个状态":指的是收到正确的消息后转换后的一个状态,说明在一个状态中必定有转移到另一个状态的接口方法。
上述蓝色加深的字已经将整个类图说的很详细了。总结如下图:
同时我们再加上在主席走路的状态下有苍蝇骚扰,再其他状态下不能骚扰。若骚扰则主席将它赶跑。
有了完整的类图,我们就可以开始完成整个代码了。由于代码有500行之多,接下来我会在每段代码后进行相应的讲解注释。
main.html:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> </head> <body> <input type="button" value="主席步行" id="walk" /> <input type="button" value="主席演讲" id="speech" /> <input type="button" value="群众们向主席挥手" id="waveHands" /> <input type="button" value="电视采访靠近毛主席" id="faceRe"/> <input type="button" value="电视采访向毛主席敬礼" id="SaluteToRe"/> <input type="button" value="主席命令整理队伍" id="command"/> <input type="button" value="主席开始跑步" id="run"/> <input type="button" value="苍蝇过来骚扰" id="come"/> <input type="button" value="赶走苍蝇" id="leave"/> <script type="text/javascript" src="State.js"></script> <script type="text/javascript" src="Leader.js"></script> <script type="text/javascript" src="ReadyState.js"></script> <script type="text/javascript" src="WalkingState.js"></script> <script type="text/javascript" src="SpeechState.js"></script> <script type="text/javascript" src="WaveHandsState.js"></script> <script type="text/javascript" src="FaceReporterState.js"></script> <script type="text/javascript" src="SaluteToReportState.js"></script> <script type="text/javascript" src="CommandLineUpState.js"></script> <script type="text/javascript" src="RunState.js"></script> <script type="text/javascript" src="Fly.js"></script> <script type="text/javascript" src="flyState.js"></script> <script type="text/javascript" src="leaveState.js"></script> <script type="text/javascript"> var oLeader = new Leader(); var oFly = new Fly(oLeader); var $ = function(id){ return document.getElementById(id); } $("walk").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_WalkingState(); } $("speech").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_SpeechState(); } $("waveHands").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_WaveHandsState(); }; $("faceRe").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_FaceReporterState(); }; $("SaluteToRe").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_SaluteToReportState(); }; $("command").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_CommandLineUpState(); }; $("run").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oLeader.Leader_RunState(); }; $("come").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oFly.Fly_getFlys(); }; $("leave").onclick = function(){ console.log("触发的消息------"); console.log(this.value+":"); oFly.Fly_getLeaveS(); }; </script> </body> </html>
这是html文件,包含了Leader.js和其他状态js文件。其中id="waveHands"的button是群众对象,它发送"群众向主席挥手"的消息给主席,id="faceRe","SaluteToRe"是电视采访对象,它发送"电视采访面向主席"和"电视采访致敬主席"的消息。其他的id,包括后两个都是Leader对象。
接下来,看看Leader类里面的东西:
Leader.js
function Leader(){ this.oState = new ReadyState(this); this.exWalkState = null; this.exSpeechState = null; this.exWaveHandsState = null; this.exFaceReporterState = null; this.exSaluteToReportState = null; this.exCommandLineUpState = null; this.exRunState = null; } Leader.prototype.setState = function(oState){ this.oState = oState; }; //外部接口 Leader.prototype.Leader_ReadyState = function(){ this.oState.Leader_ReadyState(); }; Leader.prototype.Leader_WalkingState = function(){ this.oState.Leader_WalkingState(); }; Leader.prototype.Leader_SpeechState = function(){ this.oState.Leader_SpeechState(); }; Leader.prototype.Leader_WaveHandsState = function(){ this.oState.Leader_WaveHandsState(); }; Leader.prototype.Leader_FaceReporterState = function(){ this.oState.Leader_FaceReporterState(); }; Leader.prototype.Leader_SaluteToReportState = function(){ this.oState.Leader_SaluteToReportState(); }; Leader.prototype.Leader_CommandLineUpState = function(){ this.oState.Leader_CommandLineUpState(); }; Leader.prototype.Leader_RunState = function(){ this.oState.Leader_RunState(); } //获取当前状态,传入当前this对象(指针) Leader.prototype.GetLeader_ReadyState = function(){ return new ReadyState(this); }; //走路状态单例 Leader.prototype.GetLeader_WalkingState = function(){ var that = this; /* var start = setTimeout(function(){ console.log("进入演讲模式"); that.setState(that.GetLeader_SpeechState()); },5000); */ function initial(){ return new WalkingState(that); } return function(){ if(!that.exWalkState){ that.exWalkState = initial(); } return that.exWalkState; }(); }; //演讲状态单例 Leader.prototype.GetLeader_SpeechState = function(){ var that = this; /* var start = setTimeout(function(){ console.log("进入走路模式"); that.setState(that.GetLeader_WalkingState()); },5000); */ function initial(){ return new SpeechState(that); } return function(){ if(!that.exSpeechState){ that.exSpeechState = initial(); } return that.exSpeechState; }(); }; //挥手状态单例 Leader.prototype.GetLeader_WaveHandsState = function(){ var that = this; function initial(){ return new WaveHandsState(that); } return function(){ if(!that.exWaveHandsState){ that.exWaveHandsState = initial(); } return that.exWaveHandsState; }(); }; //面对电视采访状态单例 Leader.prototype.GetLeader_FaceReporterState = function(){ var that = this; function initial(){ return new FaceReporterState(that); } return function(){ if(!that.exFaceReporterState){ that.exFaceReporterState = initial(); } return that.exFaceReporterState; }(); }; //向电视采访敬礼单例 Leader.prototype.GetLeader_SaluteToReportState = function(){ var that = this; function initial(){ return new SaluteToReportState(that); } return function(){ if(!that.exSaluteToReportState){ that.exSaluteToReportState = initial(); } return that.exSaluteToReportState; }(); }; //命令整队状态单例 Leader.prototype.GetLeader_CommandLineUpState = function(){ var that = this; function initial(){ return new CommandLineUpState(that); } return function(){ if(!that.exCommandLineUpState){ that.exCommandLineUpState = initial(); } return that.exCommandLineUpState; }(); }; //跑步状态 Leader.prototype.GetLeader_RunState = function(){ var that = this; function initial(){ return new RunState(that); } return function(){ if(!that.exRunState){ that.exRunState = initial(); } return that.exRunState; }(); }
需要注意的是这里的各个状态类,由于每次状态转移都会将oState指针指向新的状态,因此我们把状态对象用单例模式创建,保证状态对象的重复利用,提升性能。
接下来展示下各个状态的类的js文件:
State.js:
function State(){} //状态父类,你可以在这个原型上写上子类状态,也可以不写。
ReadyState.js
function ReadyState(leader){ State.apply(this); this.leader = leader; this.priv = 0; } ReadyState.prototype = new State(); //开始 ReadyState.prototype.Leader_WalkingState = function(){ this.leader.setState(this.leader.GetLeader_WalkingState()); console.log("主席开始走路!"); } //超时 ReadyState.prototype.Leader_SpeechState = function(){ console.log("主席还没出来呢!"); } //群众向毛主席挥手 ReadyState.prototype.Leader_WaveHandsState = function(){ console.log("主席还没出来呢!"); } //电视靠近毛主席 ReadyState.prototype.Leader_FaceReporterState = function(){ console.log("主席还没出来呢!"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 ReadyState.prototype.Leader_SaluteToReportState = function(){ console.log("主席还没出来呢!"); } //敬礼后超时 ReadyState.prototype.Leader_CommandLineUpState = function(){ console.log("主席还没出来呢!"); } //排队后 ReadyState.prototype.Leader_RunState = function(){ console.log("主席还没出来呢!"); }
ReadyState只会使用一次,因此不用单例创建,而它只有一个状态出口,并没有入口。
WalkingState.js
function WalkingState(leader){ State.apply(this); this.leader = leader; this.priv = 1; } WalkingState.prototype = new State(); //开始 WalkingState.prototype.Leader_WalkingState = function(){ console.log("主席已经在走路了!"); } //超时 WalkingState.prototype.Leader_SpeechState = function(){ this.leader.setState(this.leader.GetLeader_SpeechState()); console.log("主席开始演讲了!"); } //群众向主席挥手 WalkingState.prototype.Leader_WaveHandsState = function(){ this.leader.setState(this.leader.GetLeader_WaveHandsState()); console.log("主席向群众挥手!"); } //电视靠近主席 WalkingState.prototype.Leader_FaceReporterState = function(){ this.leader.setState(this.leader.GetLeader_FaceReporterState()); console.log("主席对着电视采访招手!"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 WalkingState.prototype.Leader_SaluteToReportState = function(){ console.log("电视并没有聚焦到主席!"); } //敬礼后超时 WalkingState.prototype.Leader_CommandLineUpState = function(){ console.log("现在还不能整理队伍!"); } //排队后 WalkingState.prototype.Leader_RunState = function(){ console.log("主席还没决定要跑!"); }
可以看到WalkingState有三个状态出口。这点可以看我们的类图。
SpeechState.js
function SpeechState(leader){ State.apply(this); this.leader = leader; this.priv = 2; } SpeechState.prototype = new State(); SpeechState.prototype.Leader_WalkingState = function(){ this.leader.setState(this.leader.GetLeader_WalkingState()); console.log("主席正在走路!"); } //超时 SpeechState.prototype.Leader_SpeechState = function(){ console.log("主席已经在演讲了!"); } //群众向主席挥手 SpeechState.prototype.Leader_WaveHandsState = function(){ console.log("主席还在演讲,没空!"); } //电视靠近主席 SpeechState.prototype.Leader_FaceReporterState = function(){ console.log("主席还在演讲,没空!"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 SpeechState.prototype.Leader_SaluteToReportState = function(){ console.log("主席还在演讲,没空!"); } //敬礼后超时 SpeechState.prototype.Leader_CommandLineUpState = function(){ console.log("主席还在演讲,没空!"); } //排队后 SpeechState.prototype.Leader_RunState = function(){ console.log("主席还在演讲,没空!"); }
WaveHandsState.js
function WaveHandsState(leader){ State.apply(this); this.leader = leader; this.priv = 3; } WaveHandsState.prototype = new State(); //开始 WaveHandsState.prototype.Leader_WalkingState = function(){ this.leader.setState(this.leader.GetLeader_WalkingState()); console.log("主席开始走路了!"); } //超时 WaveHandsState.prototype.Leader_SpeechState = function(){ console.log("主席没时间演讲呢!"); } //群众向主席挥手 WaveHandsState.prototype.Leader_WaveHandsState = function(){ console.log("主席不是正在向群众们挥手吗?"); } //电视靠近主席 WaveHandsState.prototype.Leader_FaceReporterState = function(){ console.log("主席没时间呢!"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 WaveHandsState.prototype.Leader_SaluteToReportState = function(){ console.log("主席没时间呢!"); } //敬礼后超时 WaveHandsState.prototype.Leader_CommandLineUpState = function(){ console.log("主席没时间呢!"); } //排队后 WaveHandsState.prototype.Leader_RunState = function(){ console.log("主席没时间呢!"); }
FaceReporterState.js
function FaceReporterState(leader){ State.apply(this); this.leader = leader; this.priv = 4; } FaceReporterState.prototype = new State(); //开始 FaceReporterState.prototype.Leader_WalkingState = function(){ console.log("毛主席可没闲工夫走路了!"); } //超时 FaceReporterState.prototype.Leader_SpeechState = function(){ console.log("主席可没闲功夫演讲!"); } //群众向主席挥手 FaceReporterState.prototype.Leader_WaveHandsState = function(){ console.log("主席可没闲工夫挥手!"); } //电视靠近主席 FaceReporterState.prototype.Leader_FaceReporterState = function(){ console.log("主席已经看着电视采访了!"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 FaceReporterState.prototype.Leader_SaluteToReportState = function(){ this.leader.setState(this.leader.GetLeader_SaluteToReportState()); console.log("主席对着电视采访敬礼!"); } //敬礼后超时 FaceReporterState.prototype.Leader_CommandLineUpState = function(){ console.log("主席还不能整理队伍!"); } //排队后 FaceReporterState.prototype.Leader_RunState = function(){ console.log("主席还不能开始跑!"); }
SaluteToReportState.js
function SaluteToReportState(leader){ State.apply(this); this.leader = leader; this.priv = 5; } SaluteToReportState.prototype = new State(); //开始 SaluteToReportState.prototype.Leader_WalkingState = function(){ console.log("主席可没闲工夫走路了!"); } //超时 SaluteToReportState.prototype.Leader_SpeechState = function(){ console.log("主席可没闲功夫演讲!"); } //群众向主席挥手 SaluteToReportState.prototype.Leader_WaveHandsState = function(){ console.log("主席可没闲工夫挥手!"); } //电视靠近主席 SaluteToReportState.prototype.Leader_FaceReporterState = function(){ console.log("主席已经对着电视采访敬礼了,不就是看着的么?"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 SaluteToReportState.prototype.Leader_SaluteToReportState = function(){ console.log("主席已经正在对着电视采访敬礼了!"); } //敬礼后超时 SaluteToReportState.prototype.Leader_CommandLineUpState = function(){ this.leader.setState(this.leader.GetLeader_CommandLineUpState()); console.log("主席开始命令整理队伍!"); } //排队后 SaluteToReportState.prototype.Leader_RunState = function(){ console.log("主席还不能开始跑!"); }
CommandLineUpState.js
function CommandLineUpState(leader){ State.apply(this); this.leader = leader; this.prev = 6; } CommandLineUpState.prototype = new State(); //开始 CommandLineUpState.prototype.Leader_WalkingState = function(){ console.log("主席可没闲工夫走路了!"); } //超时 CommandLineUpState.prototype.Leader_SpeechState = function(){ console.log("主席可没闲功夫演讲!"); } //群众向主席挥手 CommandLineUpState.prototype.Leader_WaveHandsState = function(){ console.log("主席可没闲工夫挥手!"); } //电视靠近主席 CommandLineUpState.prototype.Leader_FaceReporterState = function(){ console.log("主席不是对着电视采访命令整理队伍吗?"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 CommandLineUpState.prototype.Leader_SaluteToReportState = function(){ console.log("主席不是对着电视采访命令整理队伍吗?"); } //敬礼后超时 CommandLineUpState.prototype.Leader_CommandLineUpState = function(){ console.log("主席不是一直在命令整理队伍吗?"); } //排队后 CommandLineUpState.prototype.Leader_RunState = function(){ this.leader.setState(this.leader.GetLeader_RunState()); console.log("主席开始跑了起来!"); }
RunState.js
function CommandLineUpState(leader){ State.apply(this); this.leader = leader; this.prev = 6; } CommandLineUpState.prototype = new State(); //开始 CommandLineUpState.prototype.Leader_WalkingState = function(){ console.log("主席可没闲工夫走路了!"); } //超时 CommandLineUpState.prototype.Leader_SpeechState = function(){ console.log("主席可没闲功夫演讲!"); } //群众向主席挥手 CommandLineUpState.prototype.Leader_WaveHandsState = function(){ console.log("主席可没闲工夫挥手!"); } //电视靠近主席 CommandLineUpState.prototype.Leader_FaceReporterState = function(){ console.log("主席不是对着电视采访命令整理队伍吗?"); } //在靠近电视后,如果电视中的向主席敬礼,那么主席向电视敬礼 CommandLineUpState.prototype.Leader_SaluteToReportState = function(){ console.log("主席不是对着电视采访命令整理队伍吗?"); } //敬礼后超时 CommandLineUpState.prototype.Leader_CommandLineUpState = function(){ console.log("主席不是一直在命令整理队伍吗?"); } //排队后 CommandLineUpState.prototype.Leader_RunState = function(){ this.leader.setState(this.leader.GetLeader_RunState()); console.log("主席开始跑了起来!"); }
接下来是苍蝇的对象类,它只能在主席走路的时候才能进去,因此设置主席走路状态的priv为1,当苍蝇发送消息时,得到priv为1时,才能进入骚扰状态。同样此时主席才能赶走它。
Fly.js
function Fly(leader){ this.oState = new flyState(this); this.flyS = null; this.leaveS = null; this.leader = leader; } Fly.prototype.setState = function(state){ this.oState = state; } //苍蝇外部接口 Fly.prototype.Fly_getFlys = function(){ this.oState.Fly_getFlys(); }; Fly.prototype.Fly_getLeaveS = function(){ this.oState.Fly_getLeaveS(); }; //苍蝇飞过来单例状态 Fly.prototype.getFlyS = function(){ var that = this; function initial(){ return new flyState(that); } return function(){ if(!that.flyS){ that.flyS = initial(); } return that.flyS; }(); }; //苍蝇飞走的单例状态 Fly.prototype.getLeaveS = function(){ var that = this; function initial(){ return new leaveState(that); } return function(){ if(!that.leaveS){ that.leaveS = initial(); } return that.leaveS; }(); };
flyState.js
function flyState(fly){ State.apply(this); this.fly = fly; } flyState.prototype = new State(); flyState.prototype.Fly_getFlys = function(){ if(this.fly.leader.oState.priv == 1){ this.fly.setState(this.fly.getFlyS()); console.log("苍蝇飞到主席肩上!"); }else{ console.log("主席只有在走路时苍蝇才回过来!"); } }; flyState.prototype.Fly_getLeaveS = function(){ if(this.fly.leader.oState.priv == 1){ this.fly.setState(this.fly.getLeaveS()); console.log("苍蝇被赶走!"); }else{ console.log("苍蝇没过来,赶啥呢?"); } };
leaveState.js
function leaveState(fly){ State.apply(this); this.fly = fly; } leaveState.prototype = new State(); leaveState.prototype.Fly_getFlys = function(){ if(this.fly.leader.oState.priv == 1){ this.fly.setState(this.fly.getFlyS()); console.log("苍蝇飞到主席肩上!"); }else{ console.log("主席没有走路,并不能赶!"); } }; leaveState.prototype.Fly_getLeaveS = function(){ if(this.fly.leader.oState.priv == 1){ console.log("苍蝇已经被赶走了,还要赶尽杀绝!"); }else{ console.log("苍蝇没过来,赶啥呢?") } };
所有代码已经贴出来了,建议想了解js中状态模式的运用可以将代码复制下来运行。