现在需要你做一个简单是视频播放器的APP,主要有播放,暂停,停止三个功能,在没学状态机模式之前,你可能会这样来实现:
现抽象个IPlayer接口,定义好你的播放器需要实现的动作和可能的状态字段:
-
IPlayer
01.1publicinterfaceIPlayer {02.2publicstaticfinalintSTATE_PLAYING =1;03.3publicstaticfinalintSTATE_PAUSED =2;04.4publicstaticfinalintSTATE_STOPPED =3;05.506.6publicvoidpalyVedio();07.708.8publicvoidpause();09.910.10publicvoidstop();11.11}现在就可以实现IPlayer接口了:
01.1publicclassVedioPlayerimplementsIPlayer {02.2publicintmCurrentState;03.304.4@Override05.5publicvoidpalyVedio() {06.6switch(mCurrentState) {07.7caseSTATE_PLAYING:08.8System.out.println(' curent state is palying, do nothing.');09.9caseSTATE_PAUSED:10.10caseSTATE_STOPPED:11.11System.out.println('paly vedio now.');12.12break;13.13default:14.14// would it happen? who care.15.15break;16.16}17.17mCurrentState = STATE_PLAYING;18.18}19.1920.20@Override21.21publicvoidpause() {22.22switch(mCurrentState) {23.23caseSTATE_PLAYING:24.24System.out.println('pause vedio now');25.25break;26.26caseSTATE_PAUSED:27.27System.out.println(' curent state is paused, do noting.');28.28caseSTATE_STOPPED:29.29System.out.println('curent state is stopped,do noting.');30.30break;31.31default:32.32// would it happen? who care.33.33break;34.34}35.35mCurrentState = STATE_PAUSED;36.36}37.3738.38@Override39.39publicvoidstop() {40.40switch(mCurrentState) {41.41caseSTATE_PLAYING:42.42caseSTATE_PAUSED:43.43System.out.println(' stop vedio now.');44.44caseSTATE_STOPPED:45.45System.out.println('curent state is stopped,do noting.');46.46break;47.47default:48.48// would it happen? who care.49.49break;50.50}51.51mCurrentState = STATE_STOPPED;52.52}53.5354.5455.55}看着还错喔。
我们都知道,需求总是会改变的,现在你的boss需要在视频播放中(片头或者片尾什么的)可以播放一段广告。嗯,你可能会觉得没关系,只需要在接口上增加多一个方法就好了,同时增加个状态字段,修改后:
IPlayer01.1publicinterfaceIPlayer {02.2publicstaticfinalintSTATE_PLAYING =1;03.3publicstaticfinalintSTATE_PAUSED =2;04.4publicstaticfinalintSTATE_STOPPED =3;05.5publicstaticfinalintSTATE_AD =4;06.607.7publicvoidpalyVedio();08.8publicvoidpause();09.9publicvoidstop();10.10publicvoidshowAD();11.11}最后你认为只需要VedioPlayer实现增加的showAD方法就大功告成了,
01.1@Override02.2publicvoidshowAD() {03.3switch(mCurrentState) {04.4caseSTATE_AD:05.5System.out.println('curent state is AD,do noting');06.6break;07.7caseSTATE_PLAYING:08.8System.out.println('show advertisement now.');09.9break;10.10caseSTATE_PAUSED:11.11System.out.println('curent state is paused , do noting');12.12caseSTATE_STOPPED:13.13System.out.println('curent state is stopped ,do noting.');14.14break;15.15default:16.16// would it happen? who care.17.17break;18.18}19.19mCurrentState = STATE_AD;20.20}真的就完了?终于发现了,palyVedio,pause,stop三个方法中的swtich里面还需要各多加一个case的判断,纳尼!!!如果以后又增加几个状态,那么还得修改啊,而且随着状态的增加,修改的代码也会成倍的增加,简直不可想象。这种情况下,状态机模式就可以帮你个大忙了。
状态机模式:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类。

看着还是有点抽象吧,这里的Context就相当于我们的VedioPlayer类,我们继续以视频播放为例子:
首先还是实现播放,暂停,停止状态,此时的状态转换图应该是这样:

还是先抽象一个IPlayer作为上下文(Context):
01.1publicabstractclassIPlayer {02.203.3publicabstractvoidrequest(intflag);04.405.5publicabstractvoidsetState(PlayerState state);06.607.7publicabstractvoidpalyVedio();08.809.9publicabstractvoidpause();10.1011.11publicabstractvoidstop();12.1213.13publicabstractvoidshowAD();14.14}可以看到有一个setState方法,这是为了可以设置内部状态。
有了Context,我来实现State吧,这里写成一个抽线类
01.1publicabstractclassPlayerState {02.2publicfinalstaticintPLAY_OR_PAUSE=0;03.3publicfinalstaticintSTOP=1;04.4protectedIPlayer mPlayer;05.5publicPlayerState(IPlayer player) {06.6this.mPlayer=player;07.7}08.8publicabstractvoidhandle(intaction);09.9@Override10.10publicString toString() {11.11return'current state:'+this.getClass().getSimpleName();12.12}13.13}再看State的实现,我们有播放,暂停,停止三种状态,所以需要三个实现类:
PlayingState01.publicclassPlayingStateextendsPlayerState {02.publicPlayingState(IPlayer player) {03.super(player);04.}05.06.@Override07.publicvoidhandle(intaction) {08.switch(action) {09.casePlayingState.PLAY_OR_PAUSE:10.mPlayer.pause();11.mPlayer.setState(newPausedState(mPlayer));12.break;13.casePlayerState.STOP:14.mPlayer.stop();15.mPlayer.setState(newStoppedState(mPlayer));16.break;17.default:18.thrownewIllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());19.}20.}21.}
PausedState01.publicclassPausedStateextendsPlayerState {02.03.publicPausedState(IPlayer player) {04.super(player);05.}06.@Override07.publicvoidhandle(intaction) {08.switch(action) {09.casePlayingState.PLAY_OR_PAUSE:10.mPlayer.palyVedio();11.mPlayer.setState(newPlayingState(mPlayer));12.break;13.casePlayerState.STOP:14.mPlayer.stop();15.mPlayer.setState(newStoppedState(mPlayer));16.break;17.default:18.thrownewIllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());19.}20.}21.}
StoppedState01.publicclassStoppedStateextendsPlayerState {02.03.publicStoppedState(IPlayer player) {04.super(player);05.}06.07.@Override08.publicvoidhandle(intaction) {09.switch(action) {10.casePlayingState.PLAY_OR_PAUSE:11.mPlayer.palyVedio();12.mPlayer.setState(newPlayingState(mPlayer));13.break;14.default:15.thrownewIllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());16.}17.}18.}最后就是IPlayer的实现类VedioPlayer
01.publicclassVedioPlayerextendsIPlayer {02.privatePlayerState mState=newStoppedState(this);03.04.@Override05.publicvoidpalyVedio() {06.System.out.println('play vedio!');07.}08.09.@Override10.publicvoidpause() {11.System.out.println('pause vedio!');12.}13.14.@Override15.publicvoidstop() {16.System.out.println('stop vedio!');17.}18.19.// @Override20.// public void showAD() {21.// System.out.println('show AD!');22.// }23.24.@Override25.publicvoidsetState(PlayerState state) {26.mState = state;27.}28.29.@Override30.publicvoidrequest(intaction) {31.System.out.println('before action:'+ mState.toString());32.mState.handle(action);33.System.out.println('after action:'+ mState.toString());34.}35.36.}现在的代码就简洁多了,因为VedioPlayer只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理,而每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入而做出相应的操作和下一个状态,现在来验证下正确性:
01.1publicclassMain {02.203.3/**04.4 * @param args05.5 */06.6publicstaticvoidmain(String[] args) {07.7Scanner sc=newScanner(System.in);08.8IPlayer player=newVedioPlayer();09.9inti=-1;10.10while((i=sc.nextInt())!=-1){11.11player.request(i);12.12}13.13}14.1415.15}依次如下输入:

最后抛出了java.lang.IllegalArgumentException: ERROE ACTION:1,current state:StoppedState,因为在stopped状态下,又再次尝试stop,具体可以看StoppedState的实现。从流程来看,也验证了程序的正确性。
现在我们为视频播放器添加一个播放广告的状态,此时系统的状态:

上面我们提到VedioPlayer只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理。
也就是说现在的VedioPlayer再实现一个showAD的操作就可以了,剩下的就是状态们之间的事了。
1.@Override2.publicvoidshowAD() {3.System.out.println('show AD!');4.}现在增加一个ADState
01.publicclassShowADStateextendsPlayerState {02.publicShowADState(IPlayer player) {03.super(player);04.}05.@Override06.publicvoidhandle(intaction) {07.switch(action) {08.casePlayingState.PLAY_OR_PAUSE:09.mPlayer.palyVedio();10.mPlayer.setState(newPlayingState(mPlayer));11.break;12.default:13.thrownewIllegalArgumentException('ERROE ACTION:'+action+','+this.toString());14.}15.}16.17.}现在依然还没有完事,前面提到,每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入而做出相应的操作和下一个状态。
由状态图可以看到,PlayingState的下一个状态增加了一个ShowADState,所以PlayingState还需要做一点修改,如下:
01.1publicclassPlayingStateextendsPlayerState {02.2publicPlayingState(IPlayer player) {03.3super(player);04.4}05.506.6@Override07.7publicvoidhandle(intaction) {08.8switch(action) {09.9casePlayingState.PLAY_OR_PAUSE:10.10mPlayer.pause();11.11mPlayer.setState(newPausedState(mPlayer));12.12break;13.13casePlayerState.STOP:14.14mPlayer.stop();15.15mPlayer.setState(newStoppedState(mPlayer));16.16break;17.17casePlayingState.SHOW_AD:18.18mPlayer.showAD();19.19mPlayer.setState(newShowADState(mPlayer));20.20break;21.21default:22.22thrownewIllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());23.23}24.24}25.25}增加了17到20行的代码。
再来验证程序:

同样可以正确的运行。也可以看出,对于状态的增加,所带来的修改成本比没用状态机模式要小的多,特别对于状态更多的程序。
至此状态机模式也讲完了。
总结:
1.状态机模式:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类(每个状态可以做出不一样的动作);
2.拥有多个状态的对象(Context)只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理,而每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入(或者没输入)而做出相应的操作和自己下一个状态是什么即可;
3.适当的画出系统的状态转换图,可以更清晰地实现系统状态机。