1. 什么是状态模式
状态模式是一种行为型的设计模式,由一个场景类context驱动;所有的状态都通过state类继承过来。表现为如图1所示的样子。
状态模式允许通过改变对象内部的状态而改变对象的行为,整个对象表现得就好像修改了它的类一样。状态模式的每个状态子类中需要包含环境类(Context)中的所有方法的具体实现——条件语句。通过把行为和行为对应的逻辑包装到状态类里,在环境类里消除大量的逻辑判断,而不同状态的切换由继承(实现)State的状态子类去实现,当发现修改的当前对象的状态不是自己这个状态所对应的参数,则各个状态子类自己给Context类切换状态, 客户端不直接和状态类交互,客户端不需要了解状态。
2. 状态模式的实现
案例1:开灯关灯
灯有两种状态,开和关,非开即关。开和关组成一个状态机;状态的变迁由前一个状态的变化触发,可以理解为触发器。定义一个定义一个State类抽象灯的开和关;1
2
3
4
5
6class state {
public:
virtual void turnLightOn() {};
virtual void turnLightOff() {};
virtual ~state() {};
};
对应开和关分别定义两个类onState和offState:
1 | class offState : public state { |
这两个类都通过public的方式继承state类。然后在定义一个环境context类,用来承载状态的变迁,灯的开关用枚举定义,OFF->关着灯,ON->开着灯。
1 | class context { |
这样就完成了由两个状态组成的状态模式;用main.cpp实现充当client;
1 | #include"context.h" |
看一下通过上下文请求:这个与上述任何状态都没有直接连接;默认状态为关着灯,初始请求开灯,灯的状态变为ON,灯着开;第二个请求是一样的,它只输出一条消息状态并不做改变,指示正处于当前请求的状态什么也不发生。以下显示了这些请求的结果:
输出结果为:
1 | 灯开了,可以开始干活了! |
案例2:有三级亮度的灯
在正常开灯关灯的基础上增加两种状态,比如灯更亮,灯最亮的状态;按照状态模式的设计定义,我们就需要再增加两个类,分别表示更亮和最亮。相应的类的接口就需要增加一个接口;现在的状态变迁过程就是:关->亮->更亮->最亮,状态之间是单线的传递变迁。由于状态类的变迁是单向的,这里我们重构以状态类,通过turnLightSwitch()方法触发状态的变迁。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
48class state {
public:
virtual void turnLightSwitch() {};
virtual ~state() {};
};
class offState : public state {
public:
offState() { }
~offState() {}
void turnLightSwitch()
{
cout << "灯关了,可以休息了!" << endl;
}
};
class onState : public state {
public:
onState() {}
~onState() {}
void turnLightSwitch()
{
cout << "灯开了,可以开始干活了!" << endl;
}
};
class brighterState : public state {
public:
brighterState() {}
~brighterState() {}
void turnLightSwitch()
{
cout << "灯更亮一档了!" << endl;
}
};
class brightestState : public state {
public:
brightestState() {}
~brightestState() {}
void turnLightSwitch()
{
cout << "灯开到最亮了!" << endl;
}
};
那么相应的context类也需要进行调整,在构造的时候进行类的传递而不是通过外部传递构造。这样看起来更符合状态模式的设计。
1 | class context { |
同样,这里的客户端用一个main函数模拟使用:1
2
3
4
5
6
7
8
9
10
11
12int main()
{
context myContext =context();
myContext.turnOnLight();
myContext.turnOnLight();
myContext.turnOffLight();
myContext.turnOffLight();
myContext.turnOnLightBrighter();
myContext.turnOnLightBrightest();
myContext.turnOffLight();
return 0;
}
输出结果:1
2
3
4
5
6
7灯开了,可以开始干活了!
设置错误!
设置错误!
设置错误!
灯更亮一档了!
灯开到最亮了!
灯关了,可以休息了!
案例3:导航矩阵
有3*3的矩阵,也就是9个状态,不同的状态会有不同的选择;起始位置可以是其中任意一个,箭头方向表示可以运动的方向。也就是能上下左右走,但不能斜着走。首先建立状态接口,matrix,分别提供一个变迁和或者移动的方法名。因为这里虽然有9个状态,但是一个状态最多需要4个变迁。
1 | class matrix { |
1 | class context { |
3. 状态模式优劣
优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。