状态模式

1、APP抽奖活动问题

1)假如每参加一次这个活动要扣除用户50积分,中奖概率是10%。

2)奖品数量固定,抽完就不能抽奖。

3)活动有四个状态:可以抽奖、不能抽奖、发放奖品和奖品领完。


2、状态模式基本介绍

1)状态模式(State Pattern),它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题,状态和行为是一一对应的,状态之间可以相互转换。

2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。

【状态模式原理】

1)Context类为环境角色,用于维护State实例,这个示例定义当前状态。

也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。

2)State是抽象状态角色,定义一个接口封装与Context的一个特定接口相关行为。

3)ConcreteState具体的状态角色,每个子类实现一个与Context的一个状态相关行为。


3、应用实例代码实现

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// 状态抽象类
public abstract class State {
// 扣除积分-50
public abstract void deductMoney();

// 是否抽中奖品
public abstract boolean raffle();

// 发放奖品
public abstract void dispensePrize();
}

public class RaffleActivity {
State state;
int count = 0;
State noRaffleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispenseOutState = new DispenseOutState(this);

public RaffleActivity(int count) {
this.state = getNoRaffleState();
this.count = count;
}

public void deductMoney() {
state.deductMoney();
}

public void raffle() {
// 如果当前状态是抽奖成功
if(state.raffle()) {
// 领取奖品
state.dispensePrize();
}
}

// 每领一次奖品需要减少
public int getCount() {
return count--;
}

getter、setter
}

public class NoRaffleState extends State {
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;

public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}

// 当前状态可以扣积分,扣除后将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}

// 当前NoRaffleState状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}

// 当前NoRaffleState状态不能发放奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}

public class CanRaffleState extends State {
RaffleActivity activity;

public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}

@Override
public void deductMoney() {
System.out.println("已经扣除了积分,无需重复扣除");
}

// 当前NoRaffleState状态不能抽奖
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt();
// 10%中奖机会
if(num == 0) {
// 改变活动状态为发放奖品
activity.setState(activity.getDispenseState());
return true;
}else {
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRaffleState());
return false;
}
}

// 当前NoRaffleState状态不能发放奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}

public class DispenseState extends State {

RaffleActivity activity;

public DispenseState(RaffleActivity activity) {
this.activity = activity;
}

@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}

@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}

@Override
public void dispensePrize() {
if(activity.getCount() > 0) {
System.out.println("恭喜中奖了");
activity.setState(activity.getNoRaffleState());
}else {
System.out.println("很遗憾,奖品发送完了");
activity.setState(activity.getDispenseOutState());
}
}
}

// 当activity设为此状态,后面所有的活动都不能进行了
public class DispenseOutState extends State {
RaffleActivity activity;

public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}

@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}

@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}

@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}

public class Client {
public static void main(String[] args) {
RaffleActivity activity = new RaffleActivity(1);

// 我们连续抽300次奖
for(int i = 0; i < 300; i++) {
System.out.println("--------第" + (i+1) + "次抽奖-------");
// 参加抽奖,第一步点击扣除积分
activity.deductMoney();

// 第二步抽奖
activity.raffle();
}
}
}

4、状态模式在实际项目-借贷平台 源码剖析

1)借贷平台的订单,有审核-发布-抢单等步骤,随着操作的不同,会改变订单的状态,项目中的这个模块实现就会使用到状态模式。

2)通常通过if/else判断订单的状态,从而实现不同的逻辑,伪代码如下

1
2
3
4
5
6
7
if(审核) {

}else if(发布) {

}else if(接单) {

}

3)问题分析:这类代码难以应对变化,在添加一种状态时,我们需要手动添加if/else,在添加一种功能时,要对所有的状态进行判断。因此代码会变得越来越臃肿,并且一旦没有处理某个状态,便会发生极其严重的bug,难以维护。

4)代码改造

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
// 状态接口
public interface State {
// 电审
void checkEvent(Context context);

// 电审失败
void checkFailEvent(COntext context);

// 定价发布
void makePriceEvent(Context context);

// 接单
void acceptOrderEvent(Context context);

// 无人接单失败
void notPeopleAcceptEvent(Context context);

// 付款
void payOrderEvent(Context context);

// 接单有人支付失败
void orderFailureEvent(Context context);

// 反馈
void feedBackEvent(Context context);

String getCurrentState();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class AbstractState implements State {
protected static final RuntimeException EXCEPTION = new RuntimeException("操作流程不允许");
@Override
public void checkEvent(Context context) {throw EXCEPTION}
@Override
public void checkFailEvent(COntext context) {throw EXCEPTION}
@Override
public void makePriceEvent(Context context) {throw EXCEPTION}
@Override
public void acceptOrderEvent(Context context) {throw EXCEPTION}
@Override
public void notPeopleAcceptEvent(Context context) {throw EXCEPTION}
@Override
public void payOrderEvent(Context context) {throw EXCEPTION}
@Override
public void orderFailureEvent(Context context) {throw EXCEPTION}
@Override
public void feedBackEvent(Context context) {throw EXCEPTION}
@Override
public String getCurrentState() {throw EXCEPTION}
}
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
class FeedBackState extends AbstractState {
@Override
public String getCurrentState() {
return StateEnum.FEED_BACKED.getValue();
}
}

class GenerateState extends AbstractState {
@Override
public void checkEvent(Context context) {
context.setState(new ReviewState());
}

@Override
public void checkFailEvent(Context context) {
context.setState(new FeedBackState());
}


}

class PublishState extends AbstractState {
@Override
public void acceptOrderEvent(Context context) {
context.setState(new NotPayState());
}

@Override
public void notPeopleAcceptEvent(Context context) {
context.setState(new FeedBackState());
}

@Override
public String getCurrentState() {
return StateEnum.PUBLISHED.getValue();
}
}

省略。。。
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
public class Context extends AbstractState {
private State state;

@Override
public void checkEvent(Context context) {
state.checkEvent(this);
getCurrentState();
}
@Override
public void checkFailEvent(COntext context) {
state.checkFailEvent(this);
getCurrentState();
}
@Override
public void makePriceEvent(Context context) {
state.makePriceEvent(this);
getCurrentState();
}
@Override
public void acceptOrderEvent(Context context) {
state.acceptOrderEvent(this);
getCurrentState();
}
@Override
public void notPeopleAcceptEvent(Context context) {
state.notPeopleAcceptEvent(this);
getCurrentState();
}
@Override
public void payOrderEvent(Context context) {
state.payOrderEvent(this);
getCurrentState();
}
@Override
public void orderFailureEvent(Context context) {
state.orderFailureEvent(this);
getCurrentState();
}
@Override
public void feedBackEvent(Context context) {
state.feedBackEvent(this);
getCurrentState();
}
@Override
public String getCurrentState() {
System.out.println(state.getCurrentState());
return state.getCurrentState();
}

public State getState() {
return state;
}

public void setState(State state) {
this.state = state;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setState(new PublishState());
System.out.println(context.getCurrentState());

context.acceptOrderEvent(context);
context.payOrderEvent(context);

try {
context.checktFailEvent(context);
System.out.println("流程正常..");
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}

5、状态模式的注意事项和细节

1)代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中。并且可以很方便地增加新的状态,只需要改变对象状态即可改变对象的行为。

2)方便维护。允许状态转换逻辑与状态对象合成一体,将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产生很多的if-else语句,而且容易出错。

3)会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度。

4)状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱。

5)对 “开闭原则”支持较差。

6)应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式。当一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。