【设计模式系列】行为型模式之Status模式

概要
本来不准备写Status模式,因为它跟Strategy模式简直就是对孪生兄弟,类结构类似,处理方式类似,相像的几乎没有多少好说的,后来权衡了下,毕竟这也是GOF单独提出来的一种模式,而且Status模式跟Stragegy模式的关注点多少还是有些不同的。那么Status模式到底能做什么,怎么做的呢?
程序中经常会涉及到各种状态,每种状态下会有不同的处理逻辑,状态之间能进行切换,切换状态的同时也需要改变它们的行为,这种情况下我们的程序很容易陷入各种耦合的泥潭,Status模式可以帮助我们解决这类问题,使程序易于扩展,调理清晰。其实Strategy模式封装的是算法间的切换,而Status模式做的则是状态间的切换,关注点不同,但思路则是一样的。Strategy模式可以参考下文:
【设计模式系列】行为型模式之Strategy模式

目的
把各种状态独立封装,而把状态切换及相应处理隔离出来,使对象在改变内部状态时也改变对应状态的行为。

实例
开发一个项目管理系统,项目会经历各个阶段(可以看做各个状态),这里假设只考虑4个阶段:需求,设计,编码,测试。处于不同阶段时,系统可以输出不同的项目文档报告,比如需求阶段输出需求文档,设计时为设计文档,编码时为代码评审记录,测试时则是测试文档。看看基于Status模式会怎么考虑。
我们把各种阶段(状态)封装到不同的类,每个状态类都会有Report方法来处理不同的报告内容,而这些状态类不会去关心状态怎么切换,行为怎么改变,这些处理会封装到独立的Context类,类图和代码如下:


class Status {
public:
     virtual void Report() = 0;
};
class RequirementSts : public Status {
public:
     virtual void Report() {};
};
class DesignSts : public Status {
public:
     virtual void Report() {};
};
class CodingSts : public Status {
public:
     virtual void Report() {};
};
class TestingSts : public Status {
public:
     virtual void Report() {};
};
class Context {
public:
     Context(Status* sts) {
          mSts = sts;
     }
     void SetStatus(Status* sts) {
          mSts = sts;
     }
     void Report() {
          mSts->Report();
     }
private:
     Status* mSts;
};

Client可以这样去使用:
Status* require = new RequirementSts();
Context* cntxt = new Context(require);
cntxt ->Report();     // requirement report
Status* design = new DesignSts();
Context* cntxt = new Context(design);
cntxt ->Report();     // design report
......

可以看出当Context类的状态发生变化时,对应的行为也会自动变化。当然我们还能更进一步,可以把状态变化的流程也封装到Context类中,首先Context类需要包含四种状态的对象,也就是在构造函数参数中需要传递一个状态组,然后把状态切换的流程封装到NextStatus方法,Client可以通过调用NextStatus方法自动切换状态和行为,代码如下:
class Context {
public:
     Context(Status** sts, int stsCount) {
          mSts = sts;
          mCount = stsCount;
          mIndex = 0;
     }
     void NextStatus() {
          mIndex++;
          if (mIndex >= mCount) {
               mIndex = 0;
          }
     }
     void Report() {
          mSts[mIndex]->Report();
     }
private:
     Status** mSts;
     int mCount;
     int mIndex;
};
Client的调用:
Status* sts[4];
sts[0] = new RequirementSts();
sts[1] = new DesignSts();
sts[2] = new CodingSts();
sts[3] = new TestingSts();
Context* cntxt = new Context(sts);
cntxt ->Report();     // requirement report
cntxt->NextStatus();
cntxt ->Report();     // design report
cntxt->NextStatus();
cntxt ->Report();     // coding report
cntxt->NextStatus();
cntxt ->Report();     // testing report

应用
当需要涉及多状态切换以及相应行为转换的问题时都可以考虑使用Status模式,它可以让代码逻辑更清晰,耦合更少,便于扩展。但是它也有一个缺点,就是由于需要把所有状态都封装成单独的类,会使系统模块增加很多类,所以这里需要在状态逻辑切换复杂度和类膨胀之间权衡一下。当然,总的来说Status模式还是一种很常见常用,比较受欢迎的模式之一。
原文地址:https://www.cnblogs.com/secbook/p/2655099.html