1 核心思想
状态模式(State),当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
第一句话的意思是,状态模式中将各状态都封装成了独立的类,并将动作委托到代表当前状态的对象,因此行为会随着内部状态的改变而改变。
第二句话的意思是,从客户的视角来看,如果使用的对象能够改变它的行为,那么客户可能会觉得是从别的类实例化而来的。然而实际上,我们自己知道是在使用组合通过简单引用不同的状态对象来造成类改变的假象。
UML类图:
状态模式的UML类图和策略模式完全相同,但两者的适用场景不同,后面会详细介绍。
2 什么时候使用状态模式
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的情况。把状态的判断逻辑转移到表示不同状态的一系列的类中,可以把复杂的判断逻辑简化。
状态模式将特定状态相关的行为都放入一个类中,也就是类图中的各ConcreteState,这样通过定义新的子类可以很容易地增加新的状态和转换。这样做可以将于特定状态相关的行为局部化,并且将不同状态的行为分割开来。
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为,就可以考虑使用状态模式。
3 状态模式 vs 策略模式
状态模式和策略模式拥有相同的UML类图,但是两个的适用场景并不相同。
状态模式是把一群行为封装在状态对象中,context的行为可委托到那些状态对象中的某一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映context内部的状态。因此,context的行为也会随着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。可以把状态模式想象成对在context中放置很多条件判断的替代方案。通过把行为包装在状态对象中,可以在context内简单通过改变状态对象来改变context的行为。
但使用策略模式时,客户通常需要主动指定context所要组合的策略对象是哪一个。策略模式固然让代码更有弹性,可以在运行时改变策略,但对于某个context对象而言,通常只有一个最合适的策略对象。一般来说,我们把策略模式想象成除了继承之外的一种弹性替代方案。有了策略模式可以使用组合不同的对象来改变行为。
4 代码实现
状态模式的代码实现有两种方式,一种是Context内部拥有多个状态变量,任一时刻的状态随控制条件进行状态流转。另一种方式是各状态是对多个Context对象共享的,每次进行状态转换的时候都需要将Context对象传递进去。
///Context.h
#pragma once
#include <iostream>
#include "State.h"
class Context
{
private:
NoMoneyState* m_pNoMoneyState;
HasMoneyState* m_pHasMoneyState;
SoldState* m_pSoldState;
SoldoutState* m_pSoldoutState;
State* m_pCurrentState;
int m_nCount;
public:
Context(int nCount) :m_nCount(nCount)
{
m_pNoMoneyState = new NoMoneyState;
m_pHasMoneyState = new HasMoneyState;
m_pSoldState = new SoldState;
m_pSoldoutState = new SoldoutState;
m_pCurrentState = m_pSoldoutState;
if (m_nCount > 0)
{
m_pCurrentState = m_pNoMoneyState;
}
}
void SetState(State* pState)
{
m_pCurrentState = pState;
}
State* GetNoMoneyState()
{
return m_pNoMoneyState;
}
State* GetHasMoneyState()
{
return m_pHasMoneyState;
}
State* GetSoldState()
{
return m_pSoldState;
}
State* GetSoldoutState()
{
return m_pSoldoutState;
}
void ReleaseBall()
{
std::cout << "输出一颗糖果" << std::endl;
m_nCount--;
}
int GetCount()
{
return m_nCount;
}
void InsertMoney()
{
m_pCurrentState->InsertMoney(this);
}
void EjectMoney()
{
m_pCurrentState->EjectMoney(this);
}
void TurnCrank()
{
m_pCurrentState->TurnCrank(this);
}
void Dispense()
{
m_pCurrentState->Dispense(this);
}
};
//State.h
#pragma once
#include<iostream>
extern class Context;
class State
{
public:
virtual void InsertMoney(Context* pCtx)=0;
virtual void EjectMoney(Context* pCtx)=0;
virtual void TurnCrank(Context* pCtx)=0;
virtual void Dispense(Context* pCtx)=0;
};
class NoMoneyState:public State
{
public:
void InsertMoney(Context* pCtx);
void EjectMoney(Context* pCtx);
void TurnCrank(Context* pCtx);
void Dispense(Context* pCtx);
};
class HasMoneyState :public State
{
public:
void InsertMoney(Context* pCtx);
void EjectMoney(Context* pCtx);
void TurnCrank(Context* pCtx);
void Dispense(Context* pCtx);
};
class SoldState :public State
{
public:
void InsertMoney(Context* pCtx);
void EjectMoney(Context* pCtx);
void TurnCrank(Context* pCtx);
void Dispense(Context* pCtx);
};
class SoldoutState :public State
{
public:
void InsertMoney(Context* pCtx);
void EjectMoney(Context* pCtx);
void TurnCrank(Context* pCtx);
void Dispense(Context* pCtx);
};
//State.cpp
#include "State.h"
#include "Context.h"
void NoMoneyState::InsertMoney(Context* pCtx)
{
std::cout << "收到了您的付款!" << std::endl;
pCtx->SetState(pCtx->GetHasMoneyState());
}
void NoMoneyState::EjectMoney(Context* pCtx)
{
std::cout << "您尚未付款!" << std::endl;
}
void NoMoneyState::TurnCrank(Context* pCtx)
{
std::cout << "您尚未付款,无法转动曲柄!" << std::endl;
}
void NoMoneyState::Dispense(Context* pCtx)
{
std::cout << "您尚未付款,无法发放糖果!" << std::endl;
}
void HasMoneyState::InsertMoney(Context* pCtx)
{
std::cout << "您已付款,无需再付款!" << std::endl;
}
void HasMoneyState::EjectMoney(Context* pCtx)
{
std::cout << "已退款!" << std::endl;
pCtx->SetState(pCtx->GetNoMoneyState());
}
void HasMoneyState::TurnCrank(Context* pCtx)
{
std::cout << "请您转动曲柄!" << std::endl;
pCtx->SetState(pCtx->GetSoldState());
}
void HasMoneyState::Dispense(Context* pCtx)
{
std::cout << "请先转动曲柄,才能发放糖果!" << std::endl;
}
void SoldState::InsertMoney(Context* pCtx)
{
std::cout << "您已付款,无需再付款!" << std::endl;
}
void SoldState::EjectMoney(Context* pCtx)
{
std::cout << "您已转动曲柄,无法退款!" << std::endl;
}
void SoldState::TurnCrank(Context* pCtx)
{
std::cout << "您已转动曲柄,无需再次转动!" << std::endl;
}
void SoldState::Dispense(Context* pCtx)
{
pCtx->ReleaseBall();
if (pCtx->GetCount() == 0)
pCtx->SetState(pCtx->GetSoldoutState());
else
pCtx->SetState(pCtx->GetNoMoneyState());
}
void SoldoutState::InsertMoney(Context* pCtx)
{
std::cout << "糖果已售罄,无法付款!" << std::endl;
}
void SoldoutState::EjectMoney(Context* pCtx)
{
std::cout << "糖果已售罄,无法退款!" << std::endl;
}
void SoldoutState::TurnCrank(Context* pCtx)
{
std::cout << "糖果已售罄,无法转动曲柄!" << std::endl;
}
void SoldoutState::Dispense(Context* pCtx)
{
std::cout << "糖果已售罄,无法发放糖果!" << std::endl;
}
//main.cpp
// State.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include "State.h"
#include "Context.h"
int main()
{
Context *pCtx = new Context(5);
pCtx->InsertMoney();
pCtx->TurnCrank();
pCtx->Dispense();
pCtx->EjectMoney();
return 0;
}