博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
状态机模式
阅读量:5941 次
发布时间:2019-06-19

本文共 9024 字,大约阅读时间需要 30 分钟。

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

延伸阅读:

  • 1、
  • 2、
  • 3、
  • 4、
  • 5、
  • 6、
  • 7、
  • 8、

转载地址:http://tomtx.baihongyu.com/

你可能感兴趣的文章
Nginx 匹配 iphone Android 微信
查看>>
ldap
查看>>
Yum软件仓库配置
查看>>
linux 压缩与解压总结
查看>>
mysql脚本1064 - You have an error in your SQL syntax; check the manual
查看>>
nessus 本地扫描(一)
查看>>
linux服务器磁盘陈列
查看>>
python----tcp/ip http
查看>>
我的友情链接
查看>>
第一本docker书学习笔记1-3章
查看>>
一個典型僵尸網絡淺析
查看>>
vmware克隆Centos6.4虚拟机网卡无法启动问题
查看>>
dba学习
查看>>
asterisk配置
查看>>
GA操作步骤和技巧(二)——用户行为分析
查看>>
shell中while循环里使用ssh的注意事项
查看>>
SHELL获取计算机外网ip的几种写法
查看>>
博客正在搬迁中
查看>>
触发器与存储过程的区别
查看>>
我的友情链接
查看>>