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

    现在需要你做一个简单是视频播放器的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

    现在就可以实现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.适当的画出系统的状态转换图,可以更清晰地实现系统状态机。

  • 相关阅读:
    【USACO2017JAN】 Promotion Counting
    【POJ 3468】 A Simple Problem with Integers
    【HDU 1754】 I Hate It
    【Codeforces 20C】 Dijkstra?
    【POJ 2407】 Relatives
    BZOJ5249 九省联考2018IIIDX(线段树+贪心)
    BZOJ5251 八省联考2018劈配(网络流)
    BZOJ4200 NOI2015小园丁与老司机(动态规划+上下界网络流)
    BZOJ3876 AHOI/JSOI2014支线剧情(上下界网络流)
    LOJ117 有源汇有上下界最小流(上下界网络流)
  • 原文地址:https://www.cnblogs.com/firstdream/p/7088133.html
Copyright © 2011-2022 走看看