以下很多内容都来自GOF的设计模式,我仅仅根据自己的理解进行了简化,方便大家的同时也便于后期的回忆
目的:
状态模式一般是用在一个可能会有多个状态的对象中。当此对象的内部状态改变的时候,它的行为也会改变。对象看起来似乎是修改了它的类。
动机:
考虑一个简单的tcp连接的对象TCPConnection,它可能会处于连接开启,连接关闭,连接建立三种状态。当一个TCPConnection对象收到其他对象的请求的时候,它根据自己当前的状态进行不同的反映。但是我总感觉这个不好理解,后面举的另一个例子我感觉不错,例如我们打开了一个pdf文档,当选择“手型工具”的时候,拖拽鼠标的时候,屏幕会跟着移动,当选择“选择工具”的时候,拖拽鼠标,会进行屏幕中文字的选择,当选择“高亮工具”的时候,拖拽鼠标,会对选择的文本进行高亮。针对选择的不同工具,我们可以认为其当前处于不同的状态,即处于不同的状态的时候,相同的鼠标拖拽操作会呈现不同的行为。
类图:
从上文的类图中可以看出,DrawController类作为客户类,它在调用MousePressed方法的时候,实际上调用的是currentTool的HandleMousePress方法,至于currentTool指的具体是哪个工具,可以通过给DrawController加一个方法SetTool来设定相应的工具。当用户点击“选择工具”按钮,就会调用DrawController的SetTool(new SelectionTool)方法,来设置currentTool属性。
再次强调:
当客户对象处于不同的状态,会改变相应操作的具体行为,如上图,客户选择了不同的工具,调用相同的MousePressed方法,却做了不同的事情。依据状态模式,将一个个的状态封装成具体的对象,如上面类图中的三个具体工具,同时继承一个抽象类。抽象类负责提供虚接口。
代码简单实现:
Head文件
#ifndef __PATTERN_STATE_H
#define __PATTERN_STATE_H
#include <stdio.h>
#define LogPattern(fmt, ...) printf("[%4d %10s]" fmt"\n", __LINE__, __FUNCTION__, ##__VA_ARGS__ )
//基类工具,默认空实现
class GraphicTool
{
public:
virtual void HandleMousePressed(){}
protected:
private:
};
//绘图控制器,根据不同的工具,控制鼠标的行为
class DrawController
{
public:
DrawController():m_pCurTool(0){}
~DrawController(){
}
void MousePressed(){
if(m_pCurTool)
m_pCurTool->HandleMousePressed();
}
void SetTool(GraphicTool* tool);
void Initialise();
protected:
private:
GraphicTool* m_pCurTool;
};
//如果工具没有内部状态,可以设置其为单例模式,这样settool就不用频繁的new,delete了。
//高亮工具
class HighLightTool : public GraphicTool
{
public:
void HandleMousePressed();
protected:
private:
};
//选择工具
class SelectionTool : public GraphicTool
{
public:
void HandleMousePressed();
protected:
private:
};
//文本工具
class TextTool : public GraphicTool
{
public:
void HandleMousePressed();
protected:
private:
};
#endif
Cpp文件
#include "PatternState.h"
//初始化,设置默认工具.工具可以设置为单例模式
void DrawController::Initialise()
{
SetTool(new TextTool());
}
void DrawController::SetTool(GraphicTool* tool)
{
if (m_pCurTool)
{
delete m_pCurTool;
}
m_pCurTool = tool;
}
void HighLightTool::HandleMousePressed()
{
LogPattern("\n");
}
void TextTool::HandleMousePressed()
{
LogPattern("\n");
}
void SelectionTool::HandleMousePressed()
{
LogPattern("\n");
}
main文件的使用
void main()
{
DrawController draw;
draw.Initialise();
draw.MousePressed();
draw.SetTool(new SelectionTool);
draw.MousePressed();
draw.SetTool(new HighLightTool);
draw.MousePressed();
}
截屏输出结果:
可以很清楚的看到,首先通过Initialise设置默认的工具为TextTool,然后调用MousePressed,实际上调用的为TextTool::HandleMousePressed
当通过draw.SetTool(new SelectionTool);设置工具为SelectionTool,再次调用MousePressed,实际上调用的为SelectionTool::HandleMousePressed
好的思想:
扩展:从上面可以看出,状态模式非常适合于扩展新的状态,只需要继承基类,实现对应的方法即可。
切换:关于状态的切换,上面的是在外部进行切换的,通过设置不同的工具来实现。事实上,还可以在状态类的内部实现。例如现在我想实现当用户选择了SelectionTool,选择之后立刻回到默认的TextTool,我们可以在客户调用MousePressed方法的时候,将客户类传入进去(即DrawController),在HandleMousePressed的末尾,调用controller->SetTool(new TextToll);即可以恢复到默认的TextTool。这样就实现了一种状态切换在内部实现的机制。
实现:关于状态类的实现,如果状态之间切换非常频繁,那么状态类可以使用单例模式,这样仅仅在第一次创建,省去了很多的new delete开销,相应的内存开销就稍微大了一点。具体应用场景需要具体分析。