状态机模式

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

    view source print ?
    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需要在视频播放中(片头或者片尾什么的)可以播放一段广告。嗯,你可能会觉得没关系,只需要在接口上增加多一个方法就好了,同时增加个状态字段,修改后:


    加载中...
    view source print ?
    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方法就大功告成了,

    view source print ?
    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的判断,纳尼!!!如果以后又增加几个状态,那么还得修改啊,而且随着状态的增加,修改的代码也会成倍的增加,简直不可想象。这种情况下,状态机模式就可以帮你个大忙了。

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

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

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

    状态机模式_第2张图片

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

    view source print ?
    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吧,这里写成一个抽线类

    view source print ?
    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的实现,我们有播放,暂停,停止三种状态,所以需要三个实现类:


    加载中...
    view source print ?
    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 加载中...
    view source print ?
    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 加载中...
    view source print ?
    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

    view source print ?
    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方法调用),只需要交给当前的状态去处理,而每个状态不需要知道自己之前的状态是什么,只需要知道接收到什么样的输入而做出相应的操作和下一个状态,现在来验证下正确性:

    view source print ?
    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 }

    依次如下输入:

    状态机模式_第3张图片

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

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

    状态机模式_第4张图片

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

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

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

      现在增加一个ADState

    view source print ?
    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还需要做一点修改,如下:

    view source print ?
    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行的代码。

    再来验证程序:

    状态机模式_第5张图片

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

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

    总结:

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

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

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

延伸阅读:

  • 1、如何检测特定文件是否为Lock状态
  • 2、可灵活扩展的自定义Session状态存储驱动
  • 3、设计模式学习之-状态模式
  • 4、设计模式 状态模式(statepattern)详解
  • 5、设计模式 状态模式(statepattern)未使用状态模式详解
  • 6、C#设计模式(19)状态者模式(State Pattern)
  • 7、C#单例模式例子
  • 8、C#设计模式 简单工厂(simple factory pattern)

你可能感兴趣的:(状态机模式)