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