面向对象设计模式--状态模式(C++可执行代码)

状态模式

(State)

        状态 是一种行为设计模式,让你能在一个对象的内部状态变化时改变其行为,使其看上去就像改变了自身所属的类一样。

1. 问题 

        状态模式与有限状态机的概念紧密相关。

有限状态机。

        其主要思想是程序在任意时刻仅可处于几种有限的状态中。在任何一个特定状态中,程序的行为都不相同,且可瞬间从一个状态切换到另一个状态。不过,根据当前状态,程序可能会切换到另外一种状态,也可能会保持当前状态不变。这些数量有限且预先定义的状态切换规则被称为转移。

文档对象的全部状态和转移。

        你还可将该方法应用在对象上。 假如你有一个 文档Document 类。 文档可能会处于 草稿 Draft 、 审阅中Moderation 和 已发布 Published 三种状态中的一种。 文档的 publish 发布 方法在不同状态下的行为略有不同:

        • 处于 草稿 状态时,它会将文档转移到审阅中状态。

        • 处于 审阅中 状态时,如果当前用户是管理员,它会公开发布文档。

        • 处于 已发布 状态时,它不会进行任何操作。

        状态机通常由众多条件运算符( if 或 switch )实现,可根据对象的当前状态选择相应的行为。“状态”通常只是对象中的一组成员变量值。 即使你之前从未听说过有限状态机,你也很可能已经实现过状态模式。

        当我们逐步在 文档 类中添加更多状态和依赖于状态的行为后,基于条件语句的状态机就会暴露其最大的弱点。为了能根据当前状态选择完成相应行为的方法,绝大部分方法中会包含复杂的条件语句。修改其转换逻辑可能会涉及到修改所有方法中的状态条件语句,导致代码的维护工作非常艰难。

        这个问题会随着项目进行变得越发严重。我们很难在设计阶段预测到所有可能的状态和转换。随着时间推移,最初仅包含有限条件语句的简洁状态机可能会变成臃肿的一团乱麻。

2. 解决方案

        状态模式建议为对象的所有可能状态新建一个类,然后将所有状态的对应行为抽取到这些类中。

        原始对象被称为上下文(context),它并不会自行实现所有行为,而是会保存一个指向表示当前状态的状态对象的引用,且将所有与状态相关的工作委派给该对象。

文档将工作委派给一个状态对象。

        如需将上下文转换为另外一种状态,则需将当前活动的状态对象替换为另外一个代表新状态的对象。采用这种方式是有前提的:

        所有状态类都必须遵循同样的接口,而且上下文必须仅通过接口与这些对象进行交互。

        这个结构可能看上去与策略模式相似,但有一个关键性的不同——在状态模式中, 特定状态知道其他所有状态的存在,且能触发从一个状态到另一个状态的转换;策略则几乎完全不知道其他策略的存在。

3. 结构

状态模式的结构

        其中:

        *Context(上下文) 定义客户感兴趣的接口;维护一个Concrete State子类的实例, 这个实例定义当前状态。

        *State(状态) 定义一个接口以封装与Context的一个特定状态相关的行为。

        *Concrete State(具体状态子类) 每个子类实现与Context的一个状态相关的行为。

4. 实现方式

1. 确定哪些类是上下文。

        它可能是包含依赖于状态的代码的已有类;如果特定于状态的代码分散在多个类中,那么它可能是一个新的类。

2. 声明状态接口。

        虽然你可能会需要完全复制上下文中声明的所有方法,但最好是仅把关注点放在那些可能包含特定于状态的行为的方法上。

3. 为每个实际状态创建一个继承于状态接口的类。然后检查上下文中的方法并将与特定状态相关的所有代码抽取到新建的类中。

        在将代码移动到状态类的过程中,你可能会发现它依赖于上下文中的一些私有成员。你可以采用以下几种变通方式:

        ◦ 将这些成员变量或方法设为公有。

        ◦ 将需要抽取的上下文行为更改为上下文中的公有方法,然后在状态类中调用。这种方式简陋却便捷,你可以稍后再对其进行修补。

        ◦ 将状态类嵌套在上下文类中。这种方式需要你所使用的编程语言支持嵌套类。

4. 在上下文类中添加一个状态接口类型的引用成员变量,以及一个用于修改该成员变量值的公有设置器。

5. 再次检查上下文中的方法,将空的条件语句替换为相应的状态对象方法。

6. 为切换上下文状态,你需要创建某个状态类实例并将其传递给上下文。

        你可以在上下文、各种状态或客户端中完成这项工作。无论在何处完成这项工作,该类都将依赖于其所实例化的具体类。

5. 代码示例

state.h

#ifndef DESIGN_PATTERNS_STATE_H
#define DESIGN_PATTERNS_STATE_H

#include <iostream>
#include <string>
using namespace std;
//------------------------------//
class State;
//------------------------------//
class Work //"工作"类
{
public:
	Work();
	~Work();
	void SetState(State*);//设置状态
	void WriteProgram();//编写程序

public:
	bool finished_;//已完成状态
	int hour_;//时刻

private:
	State* state_;
};
//------------------------------//
class State //"状态"类
{
public:
	virtual ~State() {}
	virtual void WriteProgram(Work*) = 0;//编写程序
};

class WorkingState : public State //"工作状态"
{
	void WriteProgram(Work* work);
};

class OvertimeState : public State //"加班状态"
{
	void WriteProgram(Work* work);
};

class RestState : public State //"休息状态"
{
	void WriteProgram(Work* work);
};

class SleepingState : public State //"睡眠状态"
{
	void WriteProgram(Work* work);
};
//------------------------------//
#endif //DESIGN_PATTERNS_STATE_H

state.c

#include "state.h"
//------------------------------//
Work::Work() 
{
	state_ = new WorkingState();
}

Work::~Work() 
{
	delete state_;
}

void Work::SetState(State *state) 
{
	delete state_;
	state_ = state;
}

void Work::WriteProgram() 
{
	state_->WriteProgram(this);
}
//------------------------------//
void WorkingState::WriteProgram(Work *work) 
{
	if (work->hour_ < 17) 
	{
		cout << work->hour_ << " : 工作中。。。" << endl;
	}
	else 
	{
		work->SetState(new OvertimeState());
		work->WriteProgram();
	}
}

void OvertimeState::WriteProgram(Work *work) 
{
	if (work->finished_) 
	{
		work->SetState(new RestState());
		work->WriteProgram();
	}
	else if (work->hour_ < 21) 
	{
		cout << work->hour_ << " : 加班" << endl;
	}
	else 
	{
		work->SetState(new SleepingState());
		work->WriteProgram();
	}
}

void RestState::WriteProgram(Work *work) 
{
	cout << work->hour_ << " : 恢复休息" << endl;
}

void SleepingState::WriteProgram(Work *work) 
{
	cout << work->hour_ << " : 睡觉" << endl;
}
//------------------------------//

Main.c

//------------------------------//
#include <iostream>
#include "state.h"
using namespace std;
//------------------------------//
// Created by Cls on 2024/04/01.
//------------------------------//
int main(int argc, char *argv[])
{
	Work *work_;
	//-----------------//
	work_ = new Work();

	work_->hour_ = 15;
	work_->WriteProgram();
	cout << "------------------" << endl;

	work_->hour_ = 20;
	work_->finished_ = false;
	work_->WriteProgram();
	cout << "------------------" << endl;

	work_->hour_ = 22;
	work_->WriteProgram();
	cout << "------------------" << endl;
	
	delete work_;

	//-----------------//
	work_ = new Work();

	work_->hour_ = 20;
	work_->finished_ = true;
	work_->WriteProgram();
	cout << "------------------" << endl;

	delete work_;
	//-----------------//
	return 0;
}
//------------------------------//

打印输出

6. 应用场景

如果对象需要根据自身当前状态进行不同行为,同时状态的数量非常多且与状态相关的代码会频繁变更的话,可使用状态模式。

        模式建议你将所有特定于状态的代码抽取到一组独立的类中。这样一来,你可以在独立于其他状态的情况下添加新状态或修改已有状态,从而减少维护成本。

如果某个类需要根据成员变量的当前值改变自身行为,从而需要使用大量的条件语句时,可使用该模式。

        状态模式会将这些条件语句的分支抽取到相应状态类的方法中。同时,你还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。

当相似状态和基于条件的状态机转换中存在许多重复代码时,可使用状态模式。

        状态模式让你能够生成状态类层次结构,通过将公用代码抽取到抽象基类中来减少重复。

7. 优缺点

        √ 单一职责原则。将与特定状态相关的代码放在单独的类中。

        √ 开闭原则。无需修改已有状态类和上下文就能引入新状态。

        √ 通过消除臃肿的状态机条件语句简化上下文代码。

        × 如果状态机只有很少的几个状态,或者很少发生改变,那么应用该模式可能会显得小题大作。

8. 与其他模式的关系

        • 桥接、状态和策略(在某种程度上包括适配器)模式的接口非常相似。实际上,它们都基于组合模式——即将工作委派给其他对象,不过也各自解决了不同的问题。模式并不只是以特定方式组织代码的配方,你还可以使用它们来和其他开发者讨论模式所解决的问题。

        • 状态可被视为策略的扩展。两者都基于组合机制:它们都通过将部分工作委派给“帮手”对象来改变其在不同情景下的行为。策略使得这些对象相互之间完全独立,它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖,且允许它们自行改变在不同情景下的状态。

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值