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