状态模式(行为型)

前言


在dota即时对战中,不同的时期,战争的行为是不一样的。如在游戏战争前期,主要是辅助英雄包鸡包眼游走gank,后期英雄发育为主,一般不会参与团战,这时候主要以前期辅助英雄为主角;在游戏战争中期,后期英雄发育经过前期的发育,开始与辅助英雄抱团推塔,参加团战,辅助作用慢慢减弱,后期英雄慢慢变强;而在游戏战争的后期,经过前期的发育,中期团战金钱的积累,后期英雄成为了战场的主角,开始带领辅助英雄推高地,占领基地。像这种随着时间的推移,角色对象在不同的状态下具有不同的行为或者状态在某些情况下能够相互转换的行为,可以通过状态模式来解决。

状态模式


在状态模式中,我们将对象在每一个状态下的行为和状态转移语句封装在一个个状态类中,通过这些状态类来分散冗长的条件转移语句,让系统具有更好的灵活性和可扩展性

意图

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类,别名是状态对象

系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理

参与者

  • Context
    环境类,拥有多种状态的对象
    维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象

  • State
    抽象状态类,定义一个接口以封装与环境类Context的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法
    抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中

  • ConcreteState
    具体状态类,抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同

模式结构

state

代码实现

1.首先定义状态上下类StateContext,维护了一个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
// State Context
class StateContext
{
private:
State *m_pState; // 上下文的State引用
int m_value; // 上下文的信息
public:
StateContext(State *pState, int value) : m_pState(pState), m_value(value) {};
public:
void Request()
{
if (!NULL_POINTER(m_pState))
{
m_pState->Handle(this);
}
}
void SetState(State *pState)
{
m_pState = pState;
}
State* GetState(void)
{
return m_pState;
}
public:
int GetValue(void)
{
return m_value;
}
void SetValue(int value)
{
m_value = value;
}
};

2.再定义抽象的状态类State,并声明Handle()接口:

1
2
3
4
5
class State
{
public:
virtual void Handle(StateContext *pSC) = 0;
};

3.再定义两个具体的状态类ConcreteStateAConcreteStateB,并实现Handle()接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Concrete State A
class ConcreteStateA : public State
{
public:
virtual void Handle(StateContext *pSC)
{
cout << "value is " << pSC->GetValue() << endl;
cout << "Concrete State A Handle!!!" << endl;
}
};

// Concrete State B
class ConcreteStateB : public State
{
public:
virtual void Handle(StateContext *pSC)
{
cout << "value is " << pSC->GetValue() << endl;
cout << "Concrete State B Handle!!!" << endl;
}
};

4.测试状态模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void StateTest_General()
{
State *pStateA = new ConcreteStateA();
State *pStateB = new ConcreteStateB();

StateContext *pSC = new StateContext(pStateA, 10); // 构造注入ConcreteStateA
pSC->Request();

// 在客户端根据上下文信息进行状态选择切换
if (pSC->GetValue() <= 10)
{
pSC->SetState(pStateB);
}
else
{
pSC->SetState(pStateA);
}
pSC->Request();

SAFE_RELASE_POINTER(pStateA);
SAFE_RELASE_POINTER(pStateB);
SAFE_RELASE_POINTER(pSC);
}

5.运行结果:

value is 10
Concrete State A Handle!!!
value is 10
Concrete State B Handle!!!

改进版

上述,是在客户端中根据上下文信息对状态进行切换,比较好的做法是将状态切换放在状态上下文类或具体的状态类中

改进版1-在StateContext中切换状态

代码如下:

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
class StateContextEx
{
private:
StateEx *m_pCurrentState;
ConcreteStateExA *m_pStateA;
ConcreteStateExB *m_pStateB;
int m_value;
public:
StateContextEx(int value = 10)
{
m_pStateA = new ConcreteStateExA();
m_pStateB = new ConcreteStateExB();
m_pCurrentState = m_pStateA; // 初始化为状态A
m_value = value;
}
virtual ~StateContextEx()
{
SAFE_RELASE_POINTER(m_pStateA);
SAFE_RELASE_POINTER(m_pStateB);
}
public:
void SetState(StateEx *pState)
{
m_pCurrentState = pState;
}
void SetValue(int value)
{
m_value = value;
}
int GetValue(void)
{
return m_value;
}
public:
void Request() // 在状态上下文中进行判断和切换
{
if (GetValue() < 10)
{
SetState(m_pStateA);
}
else
{
SetState(m_pStateB);
}
m_pCurrentState->Handle();
}
};

改进版2-在具体State中切换状态

1.具体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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// Concrete State A
class ConcreteStateA : public State
{
public:
virtual void Handle(StateContext *pSC)
{
if (pSC->GetValue() < 10)
{
cout << "Concrete State A Handle!!!" << endl;
}
else if(pSC->GetValue() < 20)
{
pSC->SetState(new ConcreteStateB); // 可以优化为单例模式
pSC->GetState()->Handle(pSC);
}
else
{
pSC->SetState(new ConcreteStateC); // 可以优化为单例模式
pSC->GetState()->Handle(pSC);
}
}
};

// Concrete State B
class ConcreteStateB : public State
{
public:
virtual void Handle(StateContext *pSC)
{
if (pSC->GetValue() < 10)
{
pSC->SetState(new ConcreteStateA); // 可以优化为单例模式
pSC->GetState()->Handle(pSC);
}
else if (pSC->GetValue() < 20)
{
cout << "Concrete State B Handle!!!" << endl;
}
else
{
pSC->SetState(new ConcreteStateC); // 可以优化为单例模式
pSC->GetState()->Handle(pSC);
}
}
};

// Concrete State C
class ConcreteStateC : public State
{
public:
virtual void Handle(StateContext *pSC)
{
if (pSC->GetValue() < 10)
{
pSC->SetState(new ConcreteStateA); // 可以优化为单例模式
pSC->GetState()->Handle(pSC);
}
else if (pSC->GetValue() < 20)
{
pSC->SetState(new ConcreteStateB); // 可以优化为单例模式
pSC->GetState()->Handle(pSC);
}
else
{
cout << "Concrete State C Handle!!!" << endl;
}
}
};

2.这时候在环境上下文的设置状态接口中要增加释放内存的操作(若是单例模式则不需要):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// State Context
class StateContext
{
private:
State *m_pState; // 上下文的State引用
int m_value; // 上下文的信息
public:
StateContext(State *pState, int value) : m_pState(pState), m_value(value) {};
public:
void SetState(State *pState)
{
if(!NULL_POINTER(m_pState))
{
delete m_pState; // 先删除之前的State对象指针
}
m_pState = pState;
}

3.客户端调用:

1
2
3
4
5
6
7
8
9
10
void StateTest_GeneralEx()
{
StateContextEx *pSCEx = new StateContextEx(5);
pSCEx->Request(); // 只需Request,内部进行状态转换(包括上下文内部或具体状态内部)

pSCEx->SetValue(20);
pSCEx->Request();

SAFE_RELASE_POINTER(pSCEx);
}

使用场景

  • 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化
  • 代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强

优缺点

  • 优点
    • 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理
    • 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
    • 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
    • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
  • 缺点
    • 会增加系统中类和对象的个数,导致系统运行开销增大
    • 对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态

状态模式具体实例


Dota游戏战争状态问题

使用状态模式简单实现前言所述的Dota游戏战争状态不同英雄行为不同功能

代码实现
1.定义War上下文类War:

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
// War  (State Context)
class War
{
private:
WarState *m_pWarState;
int m_ElapsedDays;
public:
War(WarState *pWarState) : m_pWarState(pWarState), m_ElapsedDays(0) {};
public:
int GetElapsedDays()
{
return m_ElapsedDays;
}
void SetElapsedDays(int days)
{
m_ElapsedDays = days;
}
void SetWarState(WarState *pWarState)
{
m_pWarState = pWarState;
}
WarState* GetWarState()
{
return m_pWarState;
}
void RunWar() // 具体的War状态类负责状态选择和转换
{
m_pWarState->Run(this);
}

//void RunWar() // 环境变量类负责状态选择和转换
//{
// if (GetElapsedDays() < PREPHASE_DAY_MAX)
// {
// m_pWarState = PrephaseWarState::Instance();
// }
// else if (GetElapsedDays() < METAPHASE_DAY_MAX)
// {
// m_pWarState = MetaphaseWarState::Instance();
// }
// else
// {
// m_pWarState = AnaphaseWarState::Instance();
// }
// m_pWarState->Run(); // 无需传入contxt
//}

};

2.定义抽象的War状态类WarState,并声明Run()接口:

1
2
3
4
5
class WarState
{
public:
virtual void Run(War *pWar) = 0;
};

3.定义具体的三个时期的War状态类后期AnaphaseWarState,中期MetaphaseWarState及前期PrephaseWarState, 实现Run()接口,并内部根据War进行的时间进行状态选择和切换:

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
// AnaphaseWarState
class AnaphaseWarState : public WarState // 单例模式
{
private:
static AnaphaseWarState *m_pWarState;
class Garbage // 单例模式内部类
{
public:
virtual ~Garbage()
{
if (NULL_POINTER(AnaphaseWarState::Instance()))
{
delete AnaphaseWarState::Instance();
}
}
};
static Garbage g; //根据类的静态成员变量在宿主类析构的时候自动调用静态成员类的析构函数的原理来释放单例模式对象
private:
AnaphaseWarState() {};
AnaphaseWarState(const AnaphaseWarState&) {};
AnaphaseWarState& operator = (const AnaphaseWarState) {};
public:
static AnaphaseWarState* Instance()
{
if (NULL_POINTER(m_pWarState))
{
m_pWarState = new AnaphaseWarState();
}
return m_pWarState;
}
public:
virtual void Run(War *pWar)
{
cout << "Now The War Is On Anaphase State, Anaphase dota heroes become the protagonist, War is coming to end" << "[time = " << pWar->GetElapsedDays()<< "]" << endl;
}
};
AnaphaseWarState* AnaphaseWarState::m_pWarState = NULL;


// Metaphase War State
class MetaphaseWarState : public WarState // 单例模式
{
private:
static MetaphaseWarState *m_pWarState;
class Garbage
{
public:
virtual ~Garbage()
{
if (NULL_POINTER(MetaphaseWarState::Instance()))
{
delete MetaphaseWarState::Instance();
}
}
};
static Garbage g;//根据类的静态成员变量在宿主类析构的时候自动调用静态成员类的析构函数的原理来释放单例模式对象
private:
MetaphaseWarState() {};
MetaphaseWarState(const MetaphaseWarState&) {};
MetaphaseWarState& operator = (const MetaphaseWarState) {};
public:
static MetaphaseWarState* Instance()
{
if (NULL_POINTER(m_pWarState))
{
m_pWarState = new MetaphaseWarState();
}
return m_pWarState;
}
public:
virtual void Run(War *pWar) // 各个具体状态内部负责状态的选择和转换
{
if (pWar->GetElapsedDays() <= METAPHASE_DAY_MAX)
{
cout << "Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot" << "[time = " << pWar->GetElapsedDays()<< "]" << endl;
}
else
{
pWar->SetWarState(AnaphaseWarState::Instance());
pWar->GetWarState()->Run(pWar);
}
}
};
MetaphaseWarState* MetaphaseWarState::m_pWarState = NULL;

// Prephase War State
class PrephaseWarState : public WarState // 单例模式
{
private:
static PrephaseWarState *m_pWarState;
class Garbage
{
public:
virtual ~Garbage()
{
if (NULL_POINTER(PrephaseWarState::Instance()))
{
delete PrephaseWarState::Instance();
}
}
};
static Garbage g;//根据类的静态成员变量在宿主类析构的时候自动调用静态成员类的析构函数的原理来释放单例模式对象
private:
PrephaseWarState() {};
PrephaseWarState(const PrephaseWarState&) {};
PrephaseWarState& operator = (const PrephaseWarState) {};
public:
static PrephaseWarState* Instance()
{
if (NULL_POINTER(m_pWarState))
{
m_pWarState = new PrephaseWarState();
}
return m_pWarState;
}
public:
virtual void Run(War *pWar) // 各个具体状态内部负责状态的选择和转换
{
if (pWar->GetElapsedDays() <= PREPHASE_DAY_MAX)
{
cout << "Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin" << "[time = " << pWar->GetElapsedDays()<< "]" << endl;
}
else
{
pWar->SetWarState(MetaphaseWarState::Instance());
pWar->GetWarState()->Run(pWar);
}
}
};
PrephaseWarState* PrephaseWarState::m_pWarState = NULL;

4.测试状态模式:

1
2
3
4
5
6
7
8
9
10
11
12
void StateTest_War()
{
War *pW = new War(PrephaseWarState::Instance());

for (int ElapsedDays = 0; ElapsedDays < 30; ElapsedDays+=4)
{
pW->SetElapsedDays(ElapsedDays);
pW->RunWar(); // 客户端只负责调用RunWar即可,具体War状态内部进行了转换
}

SAFE_RELASE_POINTER(pW);
}

5.运行结果:

Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin[time = 0]
Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin[time = 4]
Now The War Is On Prephase State, Prephase dota heroes become the protagonist, War is coming to begin[time = 8]
Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot[time = 12]
Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot[time = 16]
Now The War Is On Metaphase State, Anaphase and Prephase dota heroes both become the protagonist, War is on hot[time = 20]
Now The War Is On Anaphase State, Anaphase dota heroes become the protagonist, War is coming to end[time = 24]
Now The War Is On Anaphase State, Anaphase dota heroes become the protagonist, War is coming to end[time = 28]