状态机模式

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

最后你认为只需要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());
        }
    }
}
PlayingState
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());
        }
    }
}
PausedState
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());
        }
    }
}
StoppedState

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

 

posted on 2014-09-29 14:03  HelloCsl  阅读(28280)  评论(10编辑  收藏  举报

导航