现在需要你做一个简单是视频播放器的APP,主要有播放,暂停,停止三个功能,在没学状态机模式之前,你可能会这样来实现:
现抽象个IPlayer接口,定义好你的播放器需要实现的动作和可能的状态字段:
1 public interface IPlayer { 2 public static final int STATE_PLAYING = 1; 3 public static final int STATE_PAUSED = 2; 4 public static final int STATE_STOPPED = 3; 5 6 public void palyVedio(); 7 8 public void pause(); 9 10 public void stop(); 11 }
现在就可以实现IPlayer接口了:
1 public class VedioPlayer implements IPlayer { 2 public int mCurrentState; 3 4 @Override 5 public void palyVedio() { 6 switch (mCurrentState) { 7 case STATE_PLAYING: 8 System.out.println(" curent state is palying, do nothing."); 9 case STATE_PAUSED: 10 case STATE_STOPPED: 11 System.out.println("paly vedio now."); 12 break; 13 default: 14 // would it happen? who care. 15 break; 16 } 17 mCurrentState = STATE_PLAYING; 18 } 19 20 @Override 21 public void pause() { 22 switch (mCurrentState) { 23 case STATE_PLAYING: 24 System.out.println("pause vedio now"); 25 break; 26 case STATE_PAUSED: 27 System.out.println(" curent state is paused, do noting."); 28 case STATE_STOPPED: 29 System.out.println("curent state is stopped,do noting."); 30 break; 31 default: 32 // would it happen? who care. 33 break; 34 } 35 mCurrentState = STATE_PAUSED; 36 } 37 38 @Override 39 public void stop() { 40 switch (mCurrentState) { 41 case STATE_PLAYING: 42 case STATE_PAUSED: 43 System.out.println(" stop vedio now."); 44 case STATE_STOPPED: 45 System.out.println("curent state is stopped,do noting."); 46 break; 47 default: 48 // would it happen? who care. 49 break; 50 } 51 mCurrentState = STATE_STOPPED; 52 } 53 54 55 }
看着还错喔。
我们都知道,需求总是会改变的,现在你的boss需要在视频播放中(片头或者片尾什么的)可以播放一段广告。嗯,你可能会觉得没关系,只需要在接口上增加多一个方法就好了,同时增加个状态字段,修改后:
1 public interface IPlayer { 2 public static final int STATE_PLAYING = 1; 3 public static final int STATE_PAUSED = 2; 4 public static final int STATE_STOPPED = 3; 5 public static final int STATE_AD = 4; 6 7 public void palyVedio(); 8 public void pause(); 9 public void stop(); 10 public void showAD(); 11 }
最后你认为只需要VedioPlayer实现增加的showAD方法就大功告成了,
1 @Override 2 public void showAD() { 3 switch (mCurrentState) { 4 case STATE_AD: 5 System.out.println("curent state is AD,do noting"); 6 break; 7 case STATE_PLAYING: 8 System.out.println("show advertisement now."); 9 break; 10 case STATE_PAUSED: 11 System.out.println("curent state is paused , do noting"); 12 case STATE_STOPPED: 13 System.out.println("curent state is stopped ,do noting."); 14 break; 15 default: 16 // would it happen? who care. 17 break; 18 } 19 mCurrentState = STATE_AD; 20 }
真的就完了?终于发现了,palyVedio,pause,stop三个方法中的swtich里面还需要各多加一个case的判断,纳尼!!!如果以后又增加几个状态,那么还得修改啊,而且随着状态的增加,修改的代码也会成倍的增加,简直不可想象。这种情况下,状态机模式就可以帮你个大忙了。
状态机模式:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类。
看着还是有点抽象吧,这里的Context就相当于我们的VedioPlayer类,我们继续以视频播放为例子:
首先还是实现播放,暂停,停止状态,此时的状态转换图应该是这样:
还是先抽象一个IPlayer作为上下文(Context):
1 public abstract class IPlayer { 2 3 public abstract void request(int flag); 4 5 public abstract void setState(PlayerState state); 6 7 public abstract void palyVedio(); 8 9 public abstract void pause(); 10 11 public abstract void stop(); 12 13 public abstract void showAD(); 14 }
可以看到有一个setState方法,这是为了可以设置内部状态。
有了Context,我来实现State吧,这里写成一个抽线类
1 public abstract class PlayerState { 2 public final static int PLAY_OR_PAUSE=0; 3 public final static int STOP=1; 4 protected IPlayer mPlayer; 5 public PlayerState(IPlayer player) { 6 this.mPlayer=player; 7 } 8 public abstract void handle(int action); 9 @Override 10 public String toString() { 11 return "current state:"+this.getClass().getSimpleName(); 12 } 13 }
再看State的实现,我们有播放,暂停,停止三种状态,所以需要三个实现类:
public class PlayingState extends PlayerState { public PlayingState(IPlayer player) { super(player); } @Override public void handle(int action) { switch (action) { case PlayingState.PLAY_OR_PAUSE: mPlayer.pause(); mPlayer.setState(new PausedState(mPlayer)); break; case PlayerState.STOP: mPlayer.stop(); mPlayer.setState(new StoppedState(mPlayer)); break; default: throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName()); } } }
public class PausedState extends PlayerState { public PausedState(IPlayer player) { super(player); } @Override public void handle(int action) { switch (action) { case PlayingState.PLAY_OR_PAUSE: mPlayer.palyVedio(); mPlayer.setState(new PlayingState(mPlayer)); break; case PlayerState.STOP: mPlayer.stop(); mPlayer.setState(new StoppedState(mPlayer)); break; default: throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName()); } } }
public class StoppedState extends PlayerState { public StoppedState(IPlayer player) { super(player); } @Override public void handle(int action) { switch (action) { case PlayingState.PLAY_OR_PAUSE: mPlayer.palyVedio(); mPlayer.setState(new PlayingState(mPlayer)); break; default: throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName()); } } }
最后就是IPlayer的实现类VedioPlayer
public class VedioPlayer extends IPlayer { private PlayerState mState=new StoppedState(this); @Override public void palyVedio() { System.out.println("play vedio!"); } @Override public void pause() { System.out.println("pause vedio!"); } @Override public void stop() { System.out.println("stop vedio!"); } // @Override // public void showAD() { // System.out.println("show AD!"); // } @Override public void setState(PlayerState state) { mState = state; } @Override public void request(int action) { System.out.println("before action:" + mState.toString()); mState.handle(action); System.out.println("after action:" + mState.toString()); } }
现在的代码就简洁多了,因为VedioPlayer只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理,而每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入而做出相应的操作和下一个状态,现在来验证下正确性:
1 public class Main { 2 3 /** 4 * @param args 5 */ 6 public static void main(String[] args) { 7 Scanner sc=new Scanner(System.in); 8 IPlayer player=new VedioPlayer(); 9 int i=-1; 10 while((i=sc.nextInt())!=-1){ 11 player.request(i); 12 } 13 } 14 15 }
依次如下输入:
最后抛出了java.lang.IllegalArgumentException: ERROE ACTION:1,current state:StoppedState,因为在stopped状态下,又再次尝试stop,具体可以看StoppedState的实现。从流程来看,也验证了程序的正确性。
现在我们为视频播放器添加一个播放广告的状态,此时系统的状态:
上面我们提到VedioPlayer只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理。
也就是说现在的VedioPlayer再实现一个showAD的操作就可以了,剩下的就是状态们之间的事了。
@Override public void showAD() { System.out.println("show AD!"); }
现在增加一个ADState
public class ShowADState extends PlayerState { public ShowADState(IPlayer player) { super(player); } @Override public void handle(int action) { switch (action) { case PlayingState.PLAY_OR_PAUSE: mPlayer.palyVedio(); mPlayer.setState(new PlayingState(mPlayer)); break; default: throw new IllegalArgumentException("ERROE ACTION:"+action+","+this.toString()); } } }
现在依然还没有完事,前面提到,每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入而做出相应的操作和下一个状态。
由状态图可以看到,PlayingState的下一个状态增加了一个ShowADState,所以PlayingState还需要做一点修改,如下:
1 public class PlayingState extends PlayerState { 2 public PlayingState(IPlayer player) { 3 super(player); 4 } 5 6 @Override 7 public void handle(int action) { 8 switch (action) { 9 case PlayingState.PLAY_OR_PAUSE: 10 mPlayer.pause(); 11 mPlayer.setState(new PausedState(mPlayer)); 12 break; 13 case PlayerState.STOP: 14 mPlayer.stop(); 15 mPlayer.setState(new StoppedState(mPlayer)); 16 break; 17 case PlayingState.SHOW_AD: 18 mPlayer.showAD(); 19 mPlayer.setState(new ShowADState(mPlayer)); 20 break; 21 default: 22 throw new IllegalArgumentException("ERROE ACTION:"+action+",current state:"+this.getClass().getSimpleName()); 23 } 24 } 25 }
增加了17到20行的代码。
再来验证程序:
同样可以正确的运行。也可以看出,对于状态的增加,所带来的修改成本比没用状态机模式要小的多,特别对于状态更多的程序。
至此状态机模式也讲完了。
总结:
1.状态机模式:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类(每个状态可以做出不一样的动作);
2.拥有多个状态的对象(Context)只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理,而每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入(或者没输入)而做出相应的操作和自己下一个状态是什么即可;
3.适当的画出系统的状态转换图,可以更清晰地实现系统状态机。