一 定义
状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。把不同状态的操作分散到不同的状态对象里去完成。在很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态,这样的对象叫做有状态的(stateful)对象,这样的对象状态是从事先定义好的一系列值中取出的。当一个这样的对象与外部事件产生互动时,其内部状态就会改变,从而使得系统的行为也随之发生变化。
二 ULM图
Context(环境类)又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象,定义客户端需要的接口,并且负责具体状态的切换。环境角色具有状态抽象角色定义的所有行为。
State(抽象状态类)它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中,负责对象状态定义,并且封装环境角色以实现状态切换。
ConcreteState(具体状态类)它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,简而言之,就是本状态下要做的事情,以及本状态如何过渡到其他状态。
三 状态模式的优点和缺点及可用场景
状态模式的优点
封装了转换规则,将所有与某个状态有关的行为放到一个状态对象中且提供了一个更好的方法来组织管理与特定状态相关的代码,将繁琐的状态条件判断语句转换为结构明朗的状态环境类,保证了可读性和可扩展性。
可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
状态模式的缺点
状态模式的使用必然会增加系统类和对象的个数。
状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
使用场景
状态模式的意图就是:允许一个对象在其内部状态改变时改变它的行为。简单来说当状态对象本身存在很多决定自身状态的条件分支时,而自身的行为又受条件分支的影响。
1) •一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
2) •代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
四 实例
1. 电梯
a. 电梯状态的切换示意图
b. ULM图
#include <iostream>
class Context;
//state
class LiftState
{
protected:
Context* context;
public:
virtual ~LiftState() = default;
void setContext(Context* context)
{
this->context = context;
}
//开电梯门
virtual void open() = 0;
//关电梯门
virtual void close() = 0;
//电梯运行
virtual void run() = 0;
//电梯停止
virtual void stop() = 0;
};
//context
class Context
{
public:
static LiftState* openningState;
static LiftState* closingState;
static LiftState* runningState;
static LiftState* stoppingState;
LiftState& getLiftState()
{
return *liftState;
}
void setLiftState(LiftState* liftState)
{
this->liftState = liftState;
//把当前的环境通知到各个实现类中
this->liftState->setContext(this);
}
void open()
{
liftState->open();
}
void close()
{
liftState->close();
}
void run()
{
liftState->run();
}
void stop()
{
liftState->stop();
}
private:
LiftState* liftState;
};
//ConcreteState
class OpenningState : public LiftState
{
public:
//打开电梯门
void open()
{
std::cout << "门开启电梯..." << std::endl;
}
//开门状态下,可以按下“关门”按钮
void close()
{
//将当前状态修改为关门状态
context->setLiftState(Context::closingState);
//关门
context->getLiftState().close();
}
//开门状态下不能运行!
void run()
{
//do nothing;
}
//开门状态下,按停止没效果
void stop()
{
//do nothing
}
};
//关门状态
class ClosingState : public LiftState
{
public:
//打开电梯门
void open()
{
//将当前状态修改为开门状态
context->setLiftState(Context::openningState);
context->getLiftState().open();
}
//关门状态
void close()
{
std::cout << "电梯门关闭..." << std::endl;
}
//关门状态,可以按“运行”按钮
void run()
{
//将当前状态修改为运行状态
context->setLiftState(Context::runningState);
context->getLiftState().run();
}
//关门状态下,可以切换到停止状态
void stop()
{
//将当前状态修改为停止状态
context->setLiftState(Context::stoppingState);
context->getLiftState().stop();
}
};
//运行状态
class RunningState : public LiftState
{
public:
//运行状态,按“开门”按钮没效课
void open()
{
//do nothing
}
//运行状态按“关门”按钮没效果
void close()
{
//do nothing;
}
//运行状态
void run()
{
std::cout << "电梯上下运行..." << std::endl;
}
//运行状态下,可按“停止”
void stop()
{
//将当前状态修改为停止状态
context->setLiftState(Context::stoppingState);
context->getLiftState().stop();
}
};
//停止状态
class StoppingState : public LiftState
{
public:
//停止状态,可以打开电梯门
void open()
{
//将当前状态修改为开门状态
context->setLiftState(Context::openningState);
context->getLiftState().open();
}
//停止状态下,可以按下“关门”按钮没效果
void close()
{
//do nothing
}
//关门状态,可以按“运行”按钮!
void run()
{
//将当前状态修改为运行状态
context->setLiftState(Context::runningState);
context->getLiftState().run();
}
//开门状态下,按停止没效果
void stop()
{
std::cout << "电梯停止了..." << std::endl;
}
};
LiftState* Context::openningState = new OpenningState();
LiftState* Context::closingState = new ClosingState();
LiftState* Context::runningState = new RunningState();
LiftState* Context::stoppingState = new StoppingState();
int main()
{
Context context;
context.setLiftState(Context::closingState);
context.open();
context.close();
context.run();
context.stop();
delete Context::openningState;
delete Context::closingState;
delete Context::runningState;
delete Context::stoppingState;
return 0;
}
2. 一个工人一天的时间分为上班,睡觉,干其他事情三个时间段。我们可以用状态模式来表示一天中在干什么事情。
#include <iostream>
#include <memory>
#include <utility>
class Worker;
class State
{
public:
virtual void doing(Worker* w) = 0;
virtual ~State() = default;
};
class WorkingState : public State
{
public:
void doing(Worker* worker);
};
class SleepingState : public State
{
public:
void doing(Worker* worker);
};
class OtherState : public State
{
public:
void doing(Worker* worker);
};
class Worker
{
public:
Worker() :state{std::make_unique<WorkingState>()}
{
}
void setState(std::unique_ptr<State> state)
{
this->state = std::move(state);
}
double getHour()
{
return hour;
}
void setHour(double hou)
{
hour = hou;
}
void requestDoing()
{
state->doing(this);
}
private:
std::unique_ptr<State> state;
double hour;
};
//各个状态doing方法的实现
void WorkingState::doing(Worker* worker)
{
if (worker->getHour() > 8 && worker->getHour() < 16)
{
std::cout << "WorkingState!" << std::endl;
}
else
{
worker->setState(std::make_unique<OtherState>());
worker->requestDoing();
}
}
void SleepingState::doing(Worker* worker)
{
if (worker->getHour() >= 21 || worker->getHour() < 5)
{
std::cout << "SleepingState!" << std::endl;
}
else
{
worker->setState(std::make_unique<OtherState>());
worker->requestDoing();
}
}
void OtherState::doing(Worker* worker)
{
if ((worker->getHour() >= 5 && worker->getHour() < 8)
|| (worker->getHour() >= 16 && worker->getHour() < 21))
{
std::cout << "OtherState!" << std::endl;
}
else if (worker->getHour() >= 21 || worker->getHour() < 5)
{
worker->setState(std::make_unique<SleepingState>());
worker->requestDoing();
}
else
{
worker->setState(std::make_unique<WorkingState>());
worker->requestDoing();
}
}
//客户代码
int main()
{
auto worker = std::make_unique<Worker>();
worker->setHour(24);
std::cout << "time 24" << std::endl;
worker->requestDoing();
worker->setHour(11.5);
std::cout << "time 11.5" << std::endl;
worker->requestDoing();
worker->setHour(19);
std::cout << "time 19" << std::endl;
worker->requestDoing();
return 0;
}
运行结果如下:
五 与其他相关模式
1)策略模式
状态模式和策略模式的实现方法非常类似,都是利用多态把一些操作分配到一组相关的简单的类中,因此很多人认为这两种模式实际上是相同的。
然而在现实世界中,策略(如促销一种商品的策略)和状态(如同一个按钮来控制一个电梯的状态,又如手机界面中一个按钮来控制手机)是两种完全不同的思想。当我们对状态和策略进行建模时,这种差异会导致完全不同的问题。例如,对状态进行建模时,状态迁移是一个核心内容;然而,在选择策略时,迁移与此毫无关系。另外,策略模式允许一个客户选择或提供一种策略,而这种思想在状态模式中完全没有。
一个策略是一个计划或方案,通过执行这个计划或方案,我们可以在给定的输入条件下达到一个特定的目标。策略是一组方案,他们可以相互替换;选择一个策略,获得策略的输出。策略模式用于随不同外部环境采取不同行为的场合。我们可以参考微软企业库底层Object Builder的创建对象的strategy实现方式。而状态模式不同,对一个状态特别重要的对象,通过状态机来建模一个对象的状态;状态模式处理的核心问题是状态的迁移,因为在对象存在很多状态情况下,对各个business flow,各个状态之间跳转和迁移过程都是及其复杂的。
例如一个工作流,审批一个文件,存在新建、提交、已修改、HR部门审批中、老板审批中、HR审批失败、老板审批失败等状态,涉及多个角色交互,涉及很多事件,这种情况下用状态模式(状态机)来建模更加合适;把各个状态和相应的实现步骤封装成一组简单的继承自一个接口或抽象类的类,通过另外的一个Context来操作他们之间的自动状态变换,通过event来自动实现各个状态之间的跳转。在整个生命周期中存在一个状态的迁移曲线,这个迁移曲线对客户是透明的。我们可以参考微软最新的WWF 状态机工作流实现思想。
在状态模式中,状态的变迁是由对象的内部条件决定,外界只需关心其接口,不必关心其状态对象的创建和转化;
而策略模式里,采取何种策略由外部条件(C)决定。
他们应用场景(目的)却不一样,State模式重在强调对象内部状态的变化改变对象的行为,Strategy模式重在外部对策略的选择,策略的选择由外部条件决定,
也就是说算法的动态的切换。但由于它们的结构是如此的相似,我们可以认为“状态模式是完全封装且自修改的策略模式”。即状态模式是封装对象内部的状态的,而策略模式是封装算法族的。
2)职责链模式
职责链模式和状态模式都可以解决If分支语句过多,
从定义来看,状态模式是一个对象的内在状态发生改变(一个对象,相对比较稳定,处理完一个对象下一个对象的处理一般都已确定),
而职责链模式是多个对象之间的改变(多个对象之间的话,就会出现某个对象不存在的现在,就像我们举例的公司请假流程,经理可能不在公司情况),这也说明他们两个模式处理的情况不同。
这两个设计模式最大的区别就是状态模式是让各个状态对象自己知道其下一个处理的对象是谁。
而职责链模式中的各个对象并不指定其下一个处理的对象到底是谁,只有在客户端才设定。
用我们通俗的编程语言来说,就是
状态模式:
相当于If else if else;
设计路线:各个State类的内部实现(相当于If,else If内的条件)
执行时通过State调用Context方法来执行。
职责链模式:
相当于Swich case
设计路线:客户设定,每个子类(case)的参数是下一个子类(case)。
使用时,向链的第一个子类的执行方法传递参数就可以。
就像对设计模式的总结,有的人采用的是状态模式,从头到尾,提前一定定义好下一个处理的对象是谁,而我采用的是职责链模式,随时都有可能调整链的顺序。