状态模式

新项目要有VOIP或者VOLTE的功能,即支持数据语音通话,这个功能之前的项目有做过。为了加速新项目的开发速度,将移植之前的代码,进行二次开发。由于我未参加这个功能的开发,所以需要了解代码逻辑和功能实现。

话机的状态很多,状态切换也比较复杂,所以功能实现上采用设计模式中的状态模式,为了更好的理解之前的代码,今天学习一下状态模式,并做一个记录。

此次学习参考LoveLion的处理对象的多种状态及其相互转换——状态模式(二),实例则使用C++重新实现

概述

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

状态模式定义如下:

状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式

在状态模式中引入了抽象状态类和具体状态类,它们是状态模式的核心,其结构如下图所示:

状态模式

在状态模式结构图中包含如下几个角色:

  • Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。

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

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

实例

此实例参考处理对象的多种状态及其相互转换——状态模式(三),是一个银行账户状态的转换问题,原始类图如下(犯懒了,没有根据新代码重新画类图):

账户状态

代码很长,如下:

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
//state.h
#ifndef STATE_
#define STATE_
#include <iostream>
#include <string>
using namespace std;

class Account;
class AccountState
{
public:
Account *m_acc;
AccountState(Account *acc);
virtual ~AccountState(){}
virtual void deposit(double amount){}
virtual void withdraw(double amount){}
virtual void computeInterest(){}
virtual void stateCheck(){}
};

class NormalState : public AccountState
{
public:
NormalState(Account *acc);
~NormalState(){}
void deposit(double amount) override;
void withdraw(double amount) override;
void computeInterest() override;
void stateCheck() override;
};

class OverDraftState : public AccountState
{
public:
OverDraftState(Account *acc);
~OverDraftState(){}
void deposit(double amount) override;
void withdraw(double amount) override;
void computeInterest() override;
void stateCheck() override;
};

class RestrictedState : public AccountState
{
public:
RestrictedState(Account *acc);
~RestrictedState(){}
void deposit(double amount) override;
void withdraw(double amount) override;
void computeInterest() override;
void stateCheck() override;
};

class Account
{
private:
string m_owner;
string now_state;
double m_balance = 0;

public:
friend class NormalState;
friend class RestrictedState;
friend class OverDraftState;

AccountState *m_state;
NormalState m_normal;
RestrictedState m_restricted;
OverDraftState m_overdraft;

static const int NORMAL_STATE = 0;
static const int RESTRICTED_STATE = 1;
static const int OVERDRAFT_STATE = 2;

public:
Account();
Account(string owner, double init);

~Account();

double getBalance();

void setBalance(double balance);
void setState(int index);
void deposit(double amount);
void withdraw(double amount);
void computeInterest();

};
#endif

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
184
185
//state.cpp
#include "state.h"
AccountState::AccountState(Account *acc)
{
m_acc = acc;
}

NormalState::NormalState(Account *acc):AccountState(acc)
{

}

void NormalState::deposit(double amount)
{
m_acc->setBalance(m_acc->getBalance() + amount);
stateCheck();
}

void NormalState::withdraw(double amount)
{
m_acc->setBalance(m_acc->getBalance() - amount);
stateCheck();
}

void NormalState::computeInterest()
{
cout << "正常状态,无需支付利息" << endl;
}

void NormalState::stateCheck()
{
if (m_acc->getBalance() > -2000 && m_acc->getBalance() <= 0)
{
m_acc->setState(m_acc->OVERDRAFT_STATE);
}
else if (m_acc->getBalance() == -2000)
{
m_acc->setState(m_acc->RESTRICTED_STATE);
}
else if (m_acc->getBalance() < -2000)
{
cout << "操作受限!" << endl;
}
}

OverDraftState::OverDraftState(Account *acc):AccountState(acc)
{

}

void OverDraftState::deposit(double amount)
{
m_acc->setBalance(m_acc->getBalance() + amount);
stateCheck();
}

void OverDraftState::withdraw(double amount)
{
m_acc->setBalance(m_acc->getBalance() - amount);
stateCheck();
}

void OverDraftState::computeInterest()
{
cout << "计算利息" << endl;
}

void OverDraftState::stateCheck()
{
if (m_acc->getBalance() > 0)
{
m_acc->setState(m_acc->NORMAL_STATE);
}
else if (m_acc->getBalance() == -2000)
{
m_acc->setState(m_acc->RESTRICTED_STATE);
}
else if (m_acc->getBalance() < -2000)
{
cout << "操作受限!" << endl;
}
}

RestrictedState::RestrictedState(Account *acc):AccountState(acc)
{

}

void RestrictedState::deposit(double amount)
{
m_acc->setBalance(m_acc->getBalance() + amount);
stateCheck();
}

void RestrictedState::withdraw(double amount)
{
cout << "操作受限,取款失败" << endl;
}

void RestrictedState::computeInterest()
{
cout << "计算利息" << endl;
}

void RestrictedState::stateCheck()
{
if (m_acc->getBalance() > 0)
{
m_acc->setState(m_acc->NORMAL_STATE);
}
else if (m_acc->getBalance() > -2000)
{
m_acc->setState(m_acc->OVERDRAFT_STATE);
}
}

Account::Account(string owner, double init) : m_normal(this), m_restricted(this), m_overdraft(this), m_state(nullptr)
{
m_owner = owner;
m_balance = init;
setState(NORMAL_STATE);
cout << m_owner << "开户,初始金额:" << m_balance << endl;
cout << "----------------------------" << endl;
}

Account::~Account()
{
}

double Account::getBalance()
{
return m_balance;
}

void Account::setBalance(double balance)
{
m_balance = balance;
}

void Account::setState(int index)
{
switch (index)
{
case NORMAL_STATE:
now_state = "NORMAL_STATE";
m_state = &m_normal;
break;
case RESTRICTED_STATE:
now_state = "RESTRICTED_STATE";
m_state = &m_restricted;
break;
case OVERDRAFT_STATE:
now_state = "OVERDRAFT_STATE";
m_state = &m_overdraft;
break;

default:
now_state = "NORMAL_STATE";
break;
}
}

void Account::deposit(double amount)
{
cout << m_owner << "存款:" << amount << endl;
m_state->deposit(amount);
cout << "余额:" << m_balance << endl;
cout << "当前账户状态为:" << now_state << endl;
cout << "----------------------------" << endl;
}

void Account::withdraw(double amount)
{
cout << m_owner << "取款:" << amount << endl;
m_state->withdraw(amount);
cout << "余额:" << m_balance << endl;
cout << "当前账户状态为:" << now_state << endl;
cout << "----------------------------" << endl;
}

void Account::computeInterest()
{
m_state->computeInterest();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//main.cpp
#include <iostream>
#include <string>
#include "state.h"

int main()
{
string name = "张三";
double balance = 0.0;
Account acc(name, balance);
acc.deposit(1000);
acc.withdraw(2000);
acc.deposit(3000);
acc.withdraw(4000);
acc.computeInterest();
return 0;
}

运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
./state
张三开户,初始金额:0
----------------------------
张三存款:1000
余额:1000
当前账户状态为:NORMAL_STATE
----------------------------
张三取款:2000
余额:-1000
当前账户状态为:OVERDRAFT_STATE
----------------------------
张三存款:3000
余额:2000
当前账户状态为:NORMAL_STATE
----------------------------
张三取款:4000
余额:-2000
当前账户状态为:RESTRICTED_STATE
----------------------------
计算利息

遇到的问题

多重定义main()

错误如下

1
2
CMakeFiles/state_test.dir/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.o: In function `main':
CMakeCXXCompilerId.cpp:(.text+0x0): multiple definition of `main'

发现是CMakeLists的GLOB_RECURSE参数导致的,修改方法如下:

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
#目录结构如下:
.
├── build
├── CMakeLists.txt
├── state.cpp
├── state.h
└── state_main.cpp

1 directory, 4 files

#--------------------------------------------------------

#CMakeLists如下:
cmake_minimum_required(VERSION 3.05)
set(CMAKE_CXX_STANDARD 11)
project(state_test
VERSION 1.0.0 # <major>[.<minor>[.<patch>]]
)
include_directories(
.
)
file(GLOB_RECURSE SRC *.cpp)
add_executable(state ${SRC})

#--------------------------------------------------------

#方法一,修改CMakeLists:
file(GLOB SRC *.cpp)

#--------------------------------------------------------

#方法二,修改目录结构:
.
├── build
├── inc
│   └── state.h
└── src
├── CMakeLists.txt
├── state.cpp
└── state_main.cpp

3 directories, 4 files

#相应的CMakeLists改为:
include_directories(
../inc
)

原因在于GLOB只会遍历当前目录,而GLOB_RECURSE会遍历当前目录及其子目录,因为build目录和CMakeLists.txt在同一级所以GLOB_RECURSE也会遍历编译目录,所以可以看如下的一条编译过程:

1
[ 75%] Building CXX object CMakeFiles/state.dir/CMakeFiles/3.5.1/CompilerIdCXX/CMakeCXXCompilerId.cpp.o

子类重载的函数调用失败

错误输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
./state
张三开户,初始金额:0
----------------------------
张三存款:1000
余额:0
当前账户状态为:NORMAL_STATE
----------------------------
张三取款:2000
余额:0
当前账户状态为:NORMAL_STATE
----------------------------
张三存款:3000
余额:0
当前账户状态为:NORMAL_STATE
----------------------------
张三取款:4000
余额:0
当前账户状态为:NORMAL_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
class AccountState
{
public:
virtual void deposit(){cout << "Base Class" << endl;}
};

class NormalState : public AccountState
{
void deposit(){cout << "NormalState!" << endl;}
};

/*
错误,此时调用的是基类方法
*/
AccountState m_state;
NormalState m_normal;

m_state = m_normal;
m_state.deposit();

/*
正确,此时调用的是子类方法
*/
AccountState *m_state;
NormalState m_normal;

m_state = &m_normal;
m_state->deposit();

状态模式
https://carl-5535.github.io/2022/04/06/设计模式/状态模式/
作者
Carl Chen
发布于
2022年4月6日
许可协议