State的定义:不同的状态,不同的行为;或者说,每个状态有着相应的行为。
一、何时使用状态模式
State模式在实际使用中比较多,适合"状态的切换"。因为我们经常会使用If elseif else 进行状态切换, 如果针对状态的这样判断切换反复出现,我们就要联想到是否可以采取State模式了。
不只是根据状态,也有根据属性。如果某个对象的属性不同,对象的行为就不一样,这点在数据库系统中出现频率比较高,我们经常会在一个数据表的尾部,加上property属性含义的字段,用以标识记录中一些特殊性质的记录,这种属性的改变(切换)又是随时可能发生的,就有可能要使用State。
在实际使用,类似开关一样的状态切换是很多的,但有时并不是那么明显,取决于你的经验和对系统的理解深度。
这里要阐述的是"开关切换状态" 和" 一般的状态判断"是有一些区别的," 一般的状态判断"也是有 if..elseif结构,例如:
if (which==1) state="hello";
else if (which==2) state="hi";
else if (which==3) state="bye";
这是一个 " 一般的状态判断",state值的不同是根据which变量来决定的,which和state没有关系。如果改成:
if (state.euqals("bye")) state="hello";
else if (state.euqals("hello")) state="hi";
else if (state.euqals("hi")) state="bye";
这就是 "开关切换状态",是将state的状态从"hello"切换到"hi",再切换到""bye";在切换到"hello",好象一个旋转开关,这种状态改变就可以使用State模式了。
如果单纯有上面一种将"hello"–>"hi"–>"bye"–>"hello"这一个方向切换,也不一定需要使用State模式,因为State模式会建立很多子类,复杂化,但是如果又发生另外一个行为:将上面的切换方向反过来切换,或者需要任意切换,就需要State了。
请看下例:
class Context{
private:
Color state=null;
public:
void push(){
//如果当前red状态 就切换到blue
if (state==Color.red) state=Color.blue;
//如果当前blue状态 就切换到green
else if (state==Color.blue) state=Color.green;
//如果当前black状态 就切换到red
else if (state==Color.black) state=Color.red;
//如果当前green状态 就切换到black
else if (state==Color.green) state=Color.black;
Sample sample=new Sample(state);
sample.operate();
}
void pull(){
//与push状态切换正好相反
if (state==Color.green) state=Color.blue;
else if (state==Color.black) state=Color.green;
else if (state==Color.blue) state=Color.red;
else if (state==Color.red) state=Color.black;
Sample2 sample2=new Sample2(state);
sample2.operate();
}
}
在上例中,我们有两个动作push推和pull拉,这两个开关动作,改变了Context颜色,至此,我们就需要使用State模式优化它。
另外注意:但就上例,state的变化,只是简单的颜色赋值,这个具体行为是很简单的,State适合巨大的具体行为,因此在,就本例,实际使用中也不一定非要使用State模式,这会增加子类的数目,简单的变复杂。
二、状态模式是什么?
状态模式是一种行为型的软件设计模式,当一个对象的内在状态改变时,其行为也随之改变。就像玩游戏的时候,不同的buff状态,角色会有不同的伤害、技能等等。
当控制一个对象状态的条件表达式过于复杂时,很适合用该模式,将复杂的判断逻辑转移到表示不同状态的系列类中,能将逻辑大大简化。
状态模式的优点:
良好封装性。每个状态的行为被封装到对应类中。
便于维护。减少了if else或switch语句的出现,适用于条件判断复杂的场景。
良好扩展性。添加状态更便捷。
状态模式的缺点:
状态数量增加,类数量也会增加,对开发者要求较高。
状态少时,应用状态模式会显得冗余。
三、
状态模式主要包含以下角色:
1. Context(上下文):定义客户感兴趣的接口,并且维护一个ConcreteState子类(具体状态)的实例(如:FreeState),这个实例定义当前状态。
2. State(状态):定义一个接口,以封装与Context的一个特定状态 相关的行为(如:enterTime\calculateFee)。
3. ConcreteState(具体状态):每一个具体状态类实现了State接口。
停车收费系统
-- 实现一个停车收费系统,根据停车时间计算费用,不同的时间段有不同的收费标准
ParkingMeter类构造函数=》FreeState类型对象=》ParkingMeter、PaidState
#include <iostream>
#include <memory>
// 状态接口类=》封装和状态相关的行为
class ParkingState {
public:
virtual void enterTime(ParkingMeter* meter, int minutes) = 0;
virtual void calculateFee(ParkingMeter* meter) = 0;
};
// 具体状态类:免费时间段
class FreeState : public ParkingState {
public:
// FreeState方法的实现
void FreeState::enterTime(ParkingMeter* meter, int minutes)
{
int newTotalMinutes = meter->getTotalMinutes() + minutes;
meter->setFee(0.0); // 免费时间段,费用为0
if (newTotalMinutes > 60)
{
// 超过60分钟,进入收费时间段
meter->setState(std::make_unique<PaidState>());
meter->addTime(newTotalMinutes - 60);
} else {
// 在免费时间段内
std::cout << "Time entered: " << minutes << " minutes. Total time: " << newTotalMinutes << " minutes." << std::endl;
}
}
void FreeState::calculateFee(ParkingMeter* meter)
{
std::cout << "Total parking fee: $" << meter->getFee() << " (Free period)" << std::endl;
}
};
// 具体状态类:收费时间段
class PaidState : public ParkingState {
public:
// PaidState方法的实现
void PaidState::enterTime(ParkingMeter* meter, int minutes)
{
int newTotalMinutes = meter->getTotalMinutes() + minutes;
meter->setFee(newTotalMinutes * 0.5); // 收费时间段,每分钟0.5美元
std::cout << "Time entered: " << minutes << " minutes. Total time: " << newTotalMinutes << " minutes." << std::endl;
}
void PaidState::calculateFee(ParkingMeter* meter)
{
std::cout << "Total parking fee: $" << meter->getFee() << " (Paid period)" << std::endl;
}
};
// 上下文类
class ParkingMeter {
private:
std::unique_ptr<ParkingState> state;
int totalMinutes;
double fee;
public:
// ParkingMeter方法的实现
ParkingMeter::ParkingMeter() : totalMinutes(0), fee(0.0)
{
state = std::make_unique<FreeState>();
}
void ParkingMeter::setState(std::unique_ptr<ParkingState> newState)
{
state = std::move(newState);
}
void ParkingMeter::addTime(int minutes)
{
state->enterTime(this, minutes);
}
void ParkingMeter::calculateFee()
{
state->calculateFee(this);
}
int ParkingMeter::getTotalMinutes() const
{
return totalMinutes;
}
void ParkingMeter::setFee(double newFee)
{
fee = newFee;
}
double ParkingMeter::getFee() const
{
return fee;
}
};
// 客户端代码
int main()
{
ParkingMeter meter;
meter.addTime(30); // 免费时间段
meter.calculateFee();
meter.addTime(40); // 转入收费时间段
meter.calculateFee();
meter.addTime(20); // 收费时间段
meter.calculateFee();
return 0;
}