设计模式-状态模式

1. 什么是状态模式

  状态模式是一种行为型的设计模式,由一个场景类context驱动;所有的状态都通过state类继承过来。表现为如图1所示的样子。
image

图1 状态模式类图

  状态模式允许通过改变对象内部的状态而改变对象的行为,整个对象表现得就好像修改了它的类一样。状态模式的每个状态子类中需要包含环境类(Context)中的所有方法的具体实现——条件语句。通过把行为和行为对应的逻辑包装到状态类里,在环境类里消除大量的逻辑判断,而不同状态的切换由继承(实现)State的状态子类去实现,当发现修改的当前对象的状态不是自己这个状态所对应的参数,则各个状态子类自己给Context类切换状态, 客户端不直接和状态类交互,客户端不需要了解状态。

2. 状态模式的实现
案例1:开灯关灯

  灯有两种状态,开和关,非开即关。开和关组成一个状态机;状态的变迁由前一个状态的变化触发,可以理解为触发器。定义一个定义一个State类抽象灯的开和关;

1
2
3
4
5
6
class state {
public:
virtual void turnLightOn() {};
virtual void turnLightOff() {};
virtual ~state() {};
};

  对应开和关分别定义两个类onState和offState:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class offState : public state {
public:
offState() { }
~offState() {}

void turnLightOn() {cout << "灯开了,可以开始干活了!" << endl;}
void turnLightOff() {cout << "灯已经关了,快点休息吧!" << endl;}
};

class onState : public state {
public:
onState() {}
~onState() {}

void turnLightOn() {cout << "灯已经开了,不用再开了!" << endl;}
void turnLightOff() {cout << "灯关了,可以休息了!" << endl;}
};

  这两个类都通过public的方式继承state类。然后在定义一个环境context类,用来承载状态的变迁,灯的开关用枚举定义,OFF->关着灯,ON->开着灯。

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
class context {
private:
state* currentType;

offState* offSte;
onState* onSte;
//map<type, state*> stateMap;
public:
context() //state* offSte,state* onSte
{
offSte = new offState();
onSte = new onState();
currentType = offSte;
}
~context()
{
delete offSte;
delete onSte;
}

void turnOnLight()//开灯
{
currentType->turnLightOn();
currentType = onSte;
}
void turnOffLight()//关灯
{
currentType->turnLightOff();
currentType = offSte;
}
};

  这样就完成了由两个状态组成的状态模式;用main.cpp实现充当client;

1
2
3
4
5
6
7
8
9
10
11
#include"context.h"
int main()
{
context myContext =context();

myContext.turnOnLight();
myContext.turnOnLight();
myContext.turnOffLight();
myContext.turnOffLight();
return 0;
}

  看一下通过上下文请求:这个与上述任何状态都没有直接连接;默认状态为关着灯,初始请求开灯,灯的状态变为ON,灯着开;第二个请求是一样的,它只输出一条消息状态并不做改变,指示正处于当前请求的状态什么也不发生。以下显示了这些请求的结果:
输出结果为:

1
2
3
4
灯开了,可以开始干活了!
灯已经开了,不用再开了!
灯关了,可以休息了!
灯已经关了,快点休息吧!
案例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
48
class 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
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
class context {
private:
state* currentType;

offState* offSte;
onState* onSte;
brighterState* brighterSte;
brightestState* brightestSte;
public:
context()
{
offSte = new offState();
onSte = new onState();
brighterSte =new brighterState();
brightestSte =new brightestState();
currentType = offSte;
}
~context()
{
delete offSte;
delete onSte;
delete brighterSte;
delete brightestSte;
}

void turnOnLight()//开灯
{
if (currentType == offSte)
{
currentType = onSte;
currentType->turnLightSwitch();
return;
}
cout << "设置错误!" << endl;
}
void turnOffLight()//关灯
{
if (currentType == brightestSte)
{
currentType = offSte;
currentType->turnLightSwitch();
return;
}
cout << "设置错误!" << endl;
}
void turnOnLightBrighter()
{
if (currentType == onSte)
{
currentType = brighterSte;
currentType->turnLightSwitch();
return;
}
cout << "设置错误!" << endl;
}
void turnOnLightBrightest()
{
if (currentType == brighterSte)
{
currentType = brightestSte;
currentType->turnLightSwitch();
return;
}
cout << "设置错误!" << endl;
}
};

同样,这里的客户端用一个main函数模拟使用:

1
2
3
4
5
6
7
8
9
10
11
12
int 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
2
3
4
5
6
7
8
9
class matrix {
public:
virtual void up() {};
virtual void down() {};
virtual void left() {};
virtual void right() {};

virtual ~matrix() {};
};
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
class context {
private:
cellMatrix_1* matrix_1;
cellMatrix_2* matrix_2;
cellMatrix_3* matrix_3;
cellMatrix_4* matrix_4;
cellMatrix_5* matrix_5;
cellMatrix_6* matrix_6;
cellMatrix_7* matrix_7;
cellMatrix_8* matrix_8;
cellMatrix_9* matrix_9;
matrix* currentState;
public:
context()
{
matrix_1 = new cellMatrix_1();
matrix_2 = new cellMatrix_2();
matrix_3 = new cellMatrix_3();
matrix_4 = new cellMatrix_4();
matrix_5 = new cellMatrix_5();
matrix_6 = new cellMatrix_6();
matrix_7 = new cellMatrix_7();
matrix_8 = new cellMatrix_8();
matrix_9 = new cellMatrix_9();
}
~context()
{
delete matrix_1;
delete matrix_2;
delete matrix_3;
delete matrix_4;
delete matrix_5;
delete matrix_6;
delete matrix_7;
delete matrix_8;
delete matrix_9;
}
void setState(matrix* state){currentState = state;}
void up() {currentState->up();}
void down() {currentState->down();}
void left() {currentState->left();}
void right(){currentState->right();}
};
3. 状态模式优劣

优点: 1、封装了转换规则。 2、枚举可能的状态,在枚举状态之前需要确定状态种类。 3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点: 1、状态模式的使用必然会增加系统类和对象的个数。 2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 3、状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

使用场景: 1、行为随状态改变而改变的场景。 2、条件、分支语句的代替者。

-------------本文结束感谢您的阅读-------------
坚持创作,坚持分享!