0%

GoF设计模式(二十一):State Pattern 状态模式

State Pattern 状态模式中,对象的行为将在其内部状态发生改变时发生变化。故该模式同样属于行为型模式

abstract.jpeg

模式思想

我们知道酒店房间通常有如下几个状态:空闲Free、预订Booked、入住Check in。而可对其进行的操作有:预订book、取消cancel、支付pay、退房check out。而各状态之间的相互转换关系如下所示。实际上,其即是一个有限状态机

figure 1.jpeg

对应到软件开发领域,即对于酒店房间Room类而言。其支持以下一些基本方法功能:预订book、取消cancel、支付pay、退房check out。但每个方法在执行时,都需要考虑该酒店房间当前的状态。例如对于预订book方法而言,其只有在该房间为空闲状态时才可以成功产生相应的效果(这里即将酒店房间状态变为预订状态),其它任何状态下执行该方法都不会对该酒店房间实例产生任何实质影响。为此,我们必须在该book方法中通过大量分支语句(if-else、switch语句)来实现该方法在不同的状态下执行不同的业务逻辑

虽然上述的实现方案也可以用,但是大量的分支语句充斥其中不够优雅。为了解决这一问题,就需要介绍我们的状态模式了。在该模式下,其将不同状态下的各方法实现独立开来,即建立多个所谓的状态类。而酒店房间Room实例所执行的方法,都是交由当前的状态实例来真正执行的。与此同时,通过后面具体的代码,我们也可以看到各状态类可通过反向持有酒店房间Room实例的引用,实现自动变更酒店房间Room实例的状态

在该模式下,其通常有以下几个角色

  • 环境角色:其即是client客户端所关心、需要的类。其内部通常会持有一个具体状态角色的实例,用以完成真正的行为。即这里的酒店房间Room类
  • 抽象状态角色:其定义了在某个状态下环境角色可执行的方法接口。即这里的状态State类
  • 具体状态角色:其是对抽象状态角色的实现。通过该角色即可实现在不同状态下client调用环境角色的同一个方法,可以表现出不同的行为。即这里的空闲状态FreeState、预订状态BookedState、入住状态CheckinState类

实现

这里,我们通过Java来实现上文的例子。首先定义一个与client直接交互的环境角色——即酒店房间Room类。可以看到其内部持有了一个状态State实例,Room实例的方法均是由具体的某个State实例来真正执行完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 环境角色: 酒店房间
*/
public class Room {
/**
* 房号
*/
private String roomId;

/**
* 房间状态
*/
private State state;

public String getRoomId() {
return roomId;
}

public void setRoomId(String roomId) {
this.roomId = roomId;
}

public Room(String roomId) {
this.roomId = roomId;
this.state = new FreeState();
}

public State getState() {
return state;
}

public void setState(State state) {
this.state = state;
}

public void book() {
state.book(this);
}

public void cancel() {
state.cancel(this);
}

public void pay() {
state.pay(this);
}

public void checkout() {
state.checkout(this);
}

}

然后,我们定义一个房间状态的接口,并在其中定义各具体状态的通用方法接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 抽象状态角色: 酒店房间的状态
*/
public interface State {

/**
* 预订
* @param room
*/
void book(Room room);

/**
* 取消预订
* @param room
*/
void cancel(Room room);

/**
* 支付
* @param room
*/
void pay(Room room);

/**
* 退房
* @param room
*/
void checkout(Room room);
}

最后,我们来实现上文所示的酒店房间各状态类。可以看到通过向State类方法中传参的形式实现自动的内部状态转换。这里所谓自动,指的是client无需通过显式的设置即可实现状态转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/**
* 具体状态角色: 空闲状态
*/
public class FreeState implements State {
@Override
public void book(Room room) {
String str = room.getRoomId() + ": 房间预订成功";
System.out.println(str);
// 房间状态转换为预订状态
room.setState( new BookedState() );
}

@Override
public void cancel(Room room) {
String str = room.getRoomId() + ": 房间为空闲状态, 无需取消预订";
System.out.println(str);
}

@Override
public void pay(Room room) {
String str = room.getRoomId() + ": 房间为空闲状态, 无需支付";
System.out.println(str);
}

@Override
public void checkout(Room room) {
String str = room.getRoomId() + ": 房间为空闲状态, 无需退房";
System.out.println(str);
}
}
...
/**
* 具体状态角色: 预订状态
*/
public class BookedState implements State {
@Override
public void book(Room room) {
String str = room.getRoomId() + ": 房间已预订, 不可重复预订";
System.out.println(str);
}

@Override
public void cancel(Room room) {
String str = room.getRoomId() + ": 房间取消预订成功";
System.out.println(str);
// 房间状态转换为空闲状态
room.setState( new FreeState() );
}

@Override
public void pay(Room room) {
String str = room.getRoomId() + ": 房间支付成功";
System.out.println(str);
// 房间状态转换为入住状态
room.setState( new CheckinState() );
}

@Override
public void checkout(Room room) {
String str = room.getRoomId() + ": 房间为预订状态, 无需退房";
System.out.println(str);
}
}
...
/**
* 具体状态角色: 入住状态
*/
public class CheckinState implements State {
@Override
public void book(Room room) {
String str = room.getRoomId() + ": 房间为入住状态, 无法预定";
System.out.println(str);
}

@Override
public void cancel(Room room) {
String str = room.getRoomId() + ": 房间为入住状态, 无法取消预定";
System.out.println(str);
}

@Override
public void pay(Room room) {
String str = room.getRoomId() + ": 房间为入住状态, 无需重复支付";
System.out.println(str);
}

@Override
public void checkout(Room room) {
String str = room.getRoomId() + ": 房间退房成功";
System.out.println(str);
// 房间状态转换为空闲状态
room.setState( new FreeState() );
}
}

好了,现在让我们来看看该模式是如何运作的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* State Patten 状态模式 Demo
*/
public class StatePatternDemo {
public static void main(String[] args) {

Room ruJia302 = new Room("如家302");

System.out.println("---------------- Test 1 ----------------");
ruJia302.book();
ruJia302.cancel();
ruJia302.book();
ruJia302.pay();
ruJia302.checkout();

System.out.println("\n---------------- Test 2 ----------------");
ruJia302.pay();
ruJia302.book();
ruJia302.book();
ruJia302.pay();
ruJia302.cancel();
ruJia302.book();
ruJia302.checkout();
}
}

测试结果如下,符合预期

figure 2.png

参考文献

  1. Head First 设计模式 弗里曼著
请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝