跟小D每日学口语

状态机模式

现在需要你做一个简单是视频播放器的APP,主要有播放,暂停,停止三个功能,在没学状态机模式之前,你可能会这样来实现:

现抽象个IPlayer接口,定义好你的播放器需要实现的动作和可能的状态字段:

 

  • 01.1 public interface IPlayer {
    02.2     public static final int STATE_PLAYING = 1;
    03.3     public static final int STATE_PAUSED = 2;
    04.4     public static final int STATE_STOPPED = 3;
    05.5
    06.6     public void palyVedio();
    07.7
    08.8     public void pause();
    09.9
    10.10     public void stop();
    11.11 }
    IPlayer

    现在就可以实现IPlayer接口了:

    01.1 public class VedioPlayer implements IPlayer {
    02.2     public int mCurrentState;
    03.3
    04.4     @Override
    05.5     public void palyVedio() {
    06.6         switch (mCurrentState) {
    07.7         case STATE_PLAYING:
    08.8             System.out.println(' curent state is palying, do nothing.');
    09.9         case STATE_PAUSED:
    10.10         case STATE_STOPPED:
    11.11             System.out.println('paly vedio now.');
    12.12             break;
    13.13         default:
    14.14             // would it happen? who care.
    15.15             break;
    16.16         }
    17.17         mCurrentState = STATE_PLAYING;
    18.18     }
    19.19
    20.20     @Override
    21.21     public void pause() {
    22.22         switch (mCurrentState) {
    23.23         case STATE_PLAYING:
    24.24             System.out.println('pause vedio now');
    25.25             break;
    26.26         case STATE_PAUSED:
    27.27             System.out.println(' curent state is paused, do noting.');
    28.28         case STATE_STOPPED:
    29.29             System.out.println('curent state is stopped,do noting.');
    30.30             break;
    31.31         default:
    32.32             // would it happen? who care.
    33.33             break;
    34.34         }
    35.35         mCurrentState = STATE_PAUSED;
    36.36     }
    37.37
    38.38     @Override
    39.39     public void stop() {
    40.40         switch (mCurrentState) {
    41.41         case STATE_PLAYING:
    42.42         case STATE_PAUSED:
    43.43             System.out.println(' stop vedio now.');
    44.44         case STATE_STOPPED:
    45.45             System.out.println('curent state is stopped,do noting.');
    46.46             break;
    47.47         default:
    48.48             // would it happen? who care.
    49.49             break;
    50.50         }
    51.51         mCurrentState = STATE_STOPPED;
    52.52     }
    53.53
    54.54
    55.55 }

    看着还错喔。

    我们都知道,需求总是会改变的,现在你的boss需要在视频播放中(片头或者片尾什么的)可以播放一段广告。嗯,你可能会觉得没关系,只需要在接口上增加多一个方法就好了,同时增加个状态字段,修改后:


    加载中...
    01.1 public interface IPlayer {
    02.2     public static final int STATE_PLAYING = 1;
    03.3     public static final int STATE_PAUSED = 2;
    04.4     public static final int STATE_STOPPED = 3;
    05.5     public static final int STATE_AD = 4;
    06.6    
    07.7     public void palyVedio();
    08.8     public void pause();
    09.9     public void stop();
    10.10     public void showAD();
    11.11 }
    IPlayer

    最后你认为只需要VedioPlayer实现增加的showAD方法就大功告成了,

    01.1     @Override
    02.2     public void showAD() {
    03.3         switch (mCurrentState) {
    04.4         case STATE_AD:
    05.5             System.out.println('curent state is AD,do noting');
    06.6             break;
    07.7         case STATE_PLAYING:
    08.8             System.out.println('show advertisement now.');
    09.9             break;
    10.10         case STATE_PAUSED:
    11.11             System.out.println('curent state is paused , do noting');
    12.12         case STATE_STOPPED:
    13.13             System.out.println('curent state is stopped ,do noting.');
    14.14             break;
    15.15         default:
    16.16             // would it happen? who care.
    17.17             break;
    18.18         }
    19.19         mCurrentState = STATE_AD;
    20.20     }

    真的就完了?终于发现了,palyVedio,pause,stop三个方法中的swtich里面还需要各多加一个case的判断,纳尼!!!如果以后又增加几个状态,那么还得修改啊,而且随着状态的增加,修改的代码也会成倍的增加,简直不可想象。这种情况下,状态机模式就可以帮你个大忙了。

    状态机模式:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类。\

    看着还是有点抽象吧,这里的Context就相当于我们的VedioPlayer类,我们继续以视频播放为例子:

    首先还是实现播放,暂停,停止状态,此时的状态转换图应该是这样:

    \

    还是先抽象一个IPlayer作为上下文(Context):

    01.1 public abstract class IPlayer {
    02.2    
    03.3     public abstract void request(int flag);
    04.4    
    05.5     public abstract void setState(PlayerState state);
    06.6    
    07.7     public abstract void palyVedio();
    08.8
    09.9     public abstract void pause();
    10.10
    11.11     public abstract void stop();
    12.12
    13.13     public abstract void showAD();
    14.14 }

    可以看到有一个setState方法,这是为了可以设置内部状态。

    有了Context,我来实现State吧,这里写成一个抽线类

    01.1 public abstract class PlayerState {
    02.2     public final static int PLAY_OR_PAUSE=0;
    03.3     public final static int STOP=1;
    04.4     protected IPlayer mPlayer;
    05.5     public PlayerState(IPlayer player) {
    06.6         this.mPlayer=player;
    07.7     }
    08.8     public abstract void handle(int action);
    09.9     @Override
    10.10     public String toString() {
    11.11         return 'current state:'+this.getClass().getSimpleName();
    12.12     }
    13.13 }

    再看State的实现,我们有播放,暂停,停止三种状态,所以需要三个实现类:


    加载中...
    01.public class PlayingState extends PlayerState {
    02.public PlayingState(IPlayer player) {
    03.super(player);
    04.}
    05. 
    06.@Override
    07.public void handle(int action) {
    08.switch (action) {
    09.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.default:
    18.throw new IllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());
    19.}
    20.}
    21.}
    PlayingState 加载中...
    01.public class PausedState extends PlayerState {
    02. 
    03.public PausedState(IPlayer player) {
    04.super(player);
    05.}
    06.@Override
    07.public void handle(int action) {
    08.switch (action) {
    09.case PlayingState.PLAY_OR_PAUSE:
    10.mPlayer.palyVedio();
    11.mPlayer.setState(new PlayingState(mPlayer));
    12.break;
    13.case PlayerState.STOP:
    14.mPlayer.stop();
    15.mPlayer.setState(new StoppedState(mPlayer));
    16.break;
    17.default:
    18.throw new IllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());
    19.}
    20.}
    21.}
    PausedState 加载中...
    01.public class StoppedState extends PlayerState {
    02. 
    03.public StoppedState(IPlayer player) {
    04.super(player);
    05.}
    06. 
    07.@Override
    08.public void handle(int action) {
    09.switch (action) {
    10.case PlayingState.PLAY_OR_PAUSE:
    11.mPlayer.palyVedio();
    12.mPlayer.setState(new PlayingState(mPlayer));
    13.break;
    14.default:
    15.throw new IllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());
    16.}
    17.}
    18.}
    StoppedState

    最后就是IPlayer的实现类VedioPlayer

    01.public class VedioPlayer extends IPlayer {
    02.private PlayerState mState=new StoppedState(this);
    03. 
    04.@Override
    05.public void palyVedio() {
    06.System.out.println('play vedio!');
    07.}
    08. 
    09.@Override
    10.public void pause() {
    11.System.out.println('pause vedio!');
    12.}
    13. 
    14.@Override
    15.public void stop() {
    16.System.out.println('stop vedio!');
    17.}
    18. 
    19.// @Override
    20.// public void showAD() {
    21.// System.out.println('show AD!');
    22.// }
    23. 
    24.@Override
    25.public void setState(PlayerState state) {
    26.mState = state;
    27.}
    28. 
    29.@Override
    30.public void request(int action) {
    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.1 public class Main {
    02.2
    03.3     /**
    04.4      * @param args
    05.5      */
    06.6     public static void main(String[] args) {
    07.7         Scanner sc=new Scanner(System.in);
    08.8         IPlayer player=new VedioPlayer();
    09.9         int i=-1;
    10.10         while((i=sc.nextInt())!=-1){
    11.11             player.request(i);
    12.12         }
    13.13     }
    14.14
    15.15 }

    依次如下输入:

    \

    最后抛出了java.lang.IllegalArgumentException: ERROE ACTION:1,current state:StoppedState,因为在stopped状态下,又再次尝试stop,具体可以看StoppedState的实现。从流程来看,也验证了程序的正确性。

    现在我们为视频播放器添加一个播放广告的状态,此时系统的状态:

    \

      上面我们提到VedioPlayer只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理。

      也就是说现在的VedioPlayer再实现一个showAD的操作就可以了,剩下的就是状态们之间的事了。

    1.@Override
    2.public void showAD() {
    3.System.out.println('show AD!');
    4.}

      现在增加一个ADState

    01.public class ShowADState extends PlayerState {
    02.public ShowADState(IPlayer player) {
    03.super(player);
    04.}
    05.@Override
    06.public void handle(int action) {
    07.switch (action) {
    08.case PlayingState.PLAY_OR_PAUSE:
    09.mPlayer.palyVedio();
    10.mPlayer.setState(new PlayingState(mPlayer));
    11.break;
    12.default:
    13.throw new IllegalArgumentException('ERROE ACTION:'+action+','+this.toString());
    14.}
    15.}
    16. 
    17.}

    现在依然还没有完事,前面提到,每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入而做出相应的操作和下一个状态。

    由状态图可以看到,PlayingState的下一个状态增加了一个ShowADState,所以PlayingState还需要做一点修改,如下:

    01.1 public class PlayingState extends PlayerState {
    02.2     public PlayingState(IPlayer player) {
    03.3         super(player);
    04.4     }
    05.5
    06.6     @Override
    07.7     public void handle(int action) {
    08.8         switch (action) {
    09.9         case PlayingState.PLAY_OR_PAUSE:
    10.10             mPlayer.pause();
    11.11             mPlayer.setState(new PausedState(mPlayer));
    12.12             break;
    13.13         case PlayerState.STOP:
    14.14             mPlayer.stop();
    15.15             mPlayer.setState(new StoppedState(mPlayer));
    16.16             break;
    17.17         case PlayingState.SHOW_AD:
    18.18             mPlayer.showAD();
    19.19             mPlayer.setState(new ShowADState(mPlayer));
    20.20             break;
    21.21         default:
    22.22             throw new IllegalArgumentException('ERROE ACTION:'+action+',current state:'+this.getClass().getSimpleName());
    23.23         }
    24.24     }
    25.25 }

    增加了17到20行的代码。

    再来验证程序:

    \

    同样可以正确的运行。也可以看出,对于状态的增加,所带来的修改成本比没用状态机模式要小的多,特别对于状态更多的程序。

    至此状态机模式也讲完了。

    总结:

    1.状态机模式:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类(每个状态可以做出不一样的动作);

    2.拥有多个状态的对象(Context)只需要实现需要的操作,每次接收输入的时候(request方法调用),只需要交给当前的状态去处理,而每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入(或者没输入)而做出相应的操作和自己下一个状态是什么即可;

    3.适当的画出系统的状态转换图,可以更清晰地实现系统状态机。

posted @ 2015-11-09 21:35  Danny Chen  阅读(1823)  评论(0编辑  收藏  举报