zoukankan      html  css  js  c++  java
  • 状态机模式

    现在需要你做一个简单是视频播放器的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.适当的画出系统的状态转换图,可以更清晰地实现系统状态机。

  • 相关阅读:
    C# 实现保留两位小数的方法
    ueditor 上传大容量视频报http请求错误的解决方法
    Python3 message提示 AttributeError: module 'tkinter' has no attribute 'messagebox'
    Laravel5 went wrong FatalErrorException in HtmlServiceProvider.php line 36
    安装eclipse中文汉化包后无法打开eclipse
    phpstudy 下开启openssl
    mysql 数据库还原出错ERROR:Unknown command '' mysql中断
    MySql启动提示:The server quit without updating PID file(…)失败
    windows 安装 setuptools
    Nginx 域名转发
  • 原文地址:https://www.cnblogs.com/zhangchenliang/p/4951232.html
Copyright © 2011-2022 走看看