在现实生活中,常常会出现这样的事例:一个人的情绪高兴时,会做出一些助人为乐的事情。情绪低落的时候,会做出一些伤天害理的事情。这里的情绪就是状态,对应做的事情就是行为。在软件开发中也是类似的,有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。

# 一、基本介绍

【1】状态(State)模式的定义: 对有状态的对象,把复杂的 “判断逻辑” 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
【2】状态模式(State Pattern): 它主要用来解决对象在多种状态转换时,需要对外输出不同的行为问题。状态和行为是一一对应的,状态之间可以相互转换。
【3】当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
【4】这种类型的设计模式属于行为型模式。

# 二、状态模式的结构

状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。状态模式包含以下主要角色:
【1】环境(Context)角色: 也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
【2】抽象状态(State)角色: 定义一个接口,用以封装环境对象中的特定状态所对应的行为。
【3】具体状态(Concrete State)角色: 实现抽象状态所对应的行为。

状态模式

# 三、状态模式的应用案例

使用状态模式解决 APP 抽奖问题:根据如下流程中的状态,完成具体的业务操作。

状态模式

【1】应用的结构类图: 精华在RaffleActive(上下文类)和状态的子类中。两者之间相互组合,减少复杂的逻辑判断。

状态模式

【2】State 接口的实现如下:

/*
 *  状态对应的抽象行为
 */
public interface State {
    //扣除积分
    public void deduceMoney();
    //是否中奖
    public boolean raffle();
    //发放奖品
    public void dispensePrize();
}
1
2
3
4
5
6
7
8
9
10
11

【3】State 子类一:扣除积分类 NoRaffleState 的实现如下,扣除成功后,将 state 设置为抽奖状态。

public class NoRaffleState implements State{
 
    //初始化时传入活动引用,扣除积分后改变其状态
    RaffleActivity active;
 
    //构造器
    public NoRaffleState(RaffleActivity active) {
        this.active = active;
    }
 
    // 当前状态可以扣分,扣分后修改状态
    @Override
    public void deduceMoney() {
        System.out.println("扣除5个积分");
        active.setState(active.getCanRaffeState());
 
    }
    @Override
    public boolean raffle() {
        System.out.println("抽了积分才能抽奖");
        return false;
    }
 
    @Override
    public void dispensePrize() {
        System.out.println("不能方法奖品");
    }
}
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

【4】 State 子类一:抽奖状态类 CanRaffeState,如果抽中设置状态为抽中状态,未抽中时设置状态为扣积分状态。

public class CanRaffeState implements State{
    RaffleActivity active;
 
    //构造器
    public CanRaffeState(RaffleActivity active) {
        this.active = active;
    }
 
    //扣积分
    @Override
    public void deduceMoney() {
        System.out.println("已扣除积分");
    }
 
    @Override
    public boolean raffle() {
        System.out.println("正在抽奖,请稍等");
        int num = new Random().nextInt(5);
        //20% 的中奖机会,中了则返回true
        if(num == 0) {
            active.setState(active.getDispenseState());
            return true;
 
        }else {
            System.out.println("很遗憾没有抽中奖品");
            active.setState(active.getNoRaffleState());
            return false;
        }
    }
 
    @Override
    public void dispensePrize() {
        System.out.println("抽奖中,不能发送奖品");
    }
}
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

【5】 State 子类一:抽中状态类 DispenseState,如果礼物未送完,则发送礼物,并设置状态为扣分状态。否则设置为礼物以发放完,且活动结束状态。

public class DispenseState implements State{
    RaffleActivity active;
 
    //构造器
    public DispenseState(RaffleActivity active) {
        this.active = active;
    }
 
    @Override
    public void deduceMoney() {
        System.out.println("不能扣积分");
    }
 
    @Override
    public boolean raffle() {
        System.out.println("不能抽奖");
        return false;
    }
 
    @Override
    public void dispensePrize() {
        if(active.getCount()>0) {
            System.out.println("恭喜中奖了,奖品已发货");
            active.setState(active.getNoRaffleState());
        }else {
            System.out.println("很遗憾,奖品已发完");
            active.setState(active.getDispenseOutState());
        }
    }
}
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

【6】 State 子类一:礼物发放完,却活动结束状态类 DispenseOutState

public class DispenseOutState implements State{
 
    @Override
    public void deduceMoney() {
        System.out.println("活动结束");
    }
 
    @Override
    public boolean raffle() {
        System.out.println("活动结束");
        return false;
    }
 
    @Override
    public void dispensePrize() {
        System.out.println("活动结束");
        System.exit(0);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

【7】上下文类 RaffleActivity,主要存储用户的状态和礼物的总记录数等重要信息。并组合所有的状态子类,传入自身对象。

public class RaffleActivity {
    //状态
    private State state;
    //奖品记录数
    private int count;
 
    //构造器,初始化上述两个属性
    public RaffleActivity(int count) {
        //客户端创建的时候,说明开始是开始抽奖状态。
        state = noRaffleState;
        this.count = count;
    }
 
    NoRaffleState noRaffleState = new NoRaffleState(this);
    CanRaffeState canRaffeState = new CanRaffeState(this);
    DispenseState dispenseState = new DispenseState(this);
    DispenseOutState dispenseOutState = new DispenseOutState();
 
    // 当前状态可以扣分,扣分后修改状态
    public void deduceMoney() {
        state.deduceMoney();
    }
    public void raffle() {
        if(state.raffle()) {
            state.dispensePrize();
        }
    }
 
    //需要注意,我们的数量应该是递减的
    public int getCount() {
        int countNum = count;
        count--;
        return countNum;
    }
 
    public void setCount(int count) {
        this.count = count;
    }
 
    public State getState() {
        return state;
    }
 
    public void setState(State state) {
        this.state = state;
    }
 
    public NoRaffleState getNoRaffleState() {
        return noRaffleState;
    }
 
    public CanRaffeState getCanRaffeState() {
        return canRaffeState;
    }
 
    public DispenseState getDispenseState() {
        return dispenseState;
    }
 
    public DispenseOutState getDispenseOutState() {
        return dispenseOutState;
    }
}
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

【8】客户端端调用类 Client,只需要调用上下文类,便可实现客户端的需求。

public class Client {
 
    public static void main(String[] args) {
        //为了演示方便,就定义只有一个奖品
        RaffleActivity activity = new RaffleActivity(1);
        for(int i=0;i<30;i++) {
            System.out.println("======第"+i+"次,抽取奖品========");
            //扣积分
            activity.deduceMoney();
            //抽奖
            activity.raffle();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 四、状态模式的特点

状态模式的主要优点如下:
【1】状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
【2】减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
【3】有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

状态模式的主要缺点如下:
【1】状态模式的使用必然会增加系统的类与对象的个数。
【2】状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。 ​

(adsbygoogle = window.adsbygoogle || []).push({});