当前位置: 首页 > news >正文

突破编程_C++_设计模式(状态模式)

1 状态模式的基本概念

C++ 中的状态模式是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为。对象看起来好像修改了它的类。状态模式将特定状态相关的行为封装在独立的类中,并将请求委托给当前状态对象来处理。状态模式通过将行为封装在单独的状态对象中,并允许状态对象根据上下文环境来更改其行为,从而使代码更加清晰和易于维护。

状态模式通常包含以下几个关键组件:

(1)Context(上下文): 它定义了客户端所感兴趣的接口。维护一个指向当前状态对象的引用,并将与状态相关的请求委托给当前状态对象处理。

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

(3)ConcreteState(具体状态): 实现 State 接口,每一类具体状态对应一个 ConcreteState 子类。每一个状态类负责实现其对应的状态所对应的行为。

2 状态模式的实现步骤

状态模式的实现步骤如下:

(1)定义状态接口:
首先,定义一个状态接口,这个接口声明了状态对象应该提供的操作。这些操作通常与对象在特定状态下的行为有关。

(2)实现具体状态类:
接着,为每个可能的状态实现一个具体状态类。这些类将继承自状态接口,并实现其中的方法。每个具体状态类将定义对象在特定状态下的行为。

(3)定义上下文类:
上下文类通常包含一个指向当前状态对象的指针或引用。上下文类还包含一些与状态相关的请求方法,这些方法将调用当前状态对象上的相应操作。上下文类负责在状态之间切换,并在状态改变时更新其内部状态指针。

(4)实现状态切换逻辑:
在上下文类中,你需要实现状态切换的逻辑。这通常涉及根据某些条件或事件来改变当前状态对象的引用。

(5)使用上下文类:
最后,客户端代码将使用上下文类来与状态模式交互。客户端不会直接操作状态对象,而是通过调用上下文类的方法来触发状态相关的行为。上下文类将负责将请求委托给当前状态对象处理。

如下为样例代码:

#include <iostream>  
#include <memory>  class Context;// State 抽象类  
class State {
public:virtual void handle() = 0;virtual ~State() = default;void setContext(Context* ctx) {context = ctx;}protected:Context* context;
};// Context 类  
class Context {
public:Context() {}void request() {// 请求被委托给当前状态对象处理  state->handle();}void setState(std::unique_ptr<State> newState) {// 更新当前状态对象  state = std::move(newState);// 设置新状态的上下文  state->setContext(this);}private:std::unique_ptr<State> state;
};// 具体状态 A  
class StateA : public State {
public:void handle() override {std::cout << "State A is handling the request." << std::endl;}
};// 具体状态 B  
class StateB : public State {
public:void handle() override {std::cout << "State B is handling the request." << std::endl;// 在某些条件下切换到 StateA  context->setState(std::make_unique<StateA>());}
};int main() 
{Context context;context.setState(std::make_unique<StateB>());// 模拟请求处理context.request();// 再次模拟请求处理,状态会切换成 StateA context.request();return 0;
}

上面代码的输出为:

State B is handling the request.
State A is handling the request.

在这个示例中,State 是一个抽象类,它定义了一个 handle 方法。StateA 和 StateB 是 State 接口的具体实现,它们分别定义了在不同状态下的行为。Context 类持有一个指向 State 对象的 unique_ptr,这个 unique_ptr 负责管理 State 对象的生命周期。

Context 的 request 方法用于触发状态处理,它简单地调用了当前状态对象的 handle 方法。setState 方法用于在状态之间切换,它接受一个新的状态对象的 unique_ptr,并将当前状态更新为新的状态对象。

在 StateB 的handle方法中,模拟了状态切换的逻辑,并在某些条件下调用 context->setState 来切换到另一个状态。

3 状态模式的应用场景

C++ 的状态模式特别适用于当一个对象的行为取决于它的状态,并且状态之间转换复杂或者状态数量较多的情况。以下是一些状态模式的应用场景:

(1)用户界面交互: 在图形用户界面(GUI)中,按钮或控件的行为可能会根据用户交互或上下文状态的不同而变化。例如,一个按钮可能在禁用状态下显示灰色,在点击时改变颜色,并在执行某个动作后恢复默认状态。状态模式可以管理这些不同状态之间的转换和对应的行为。

(2)网络协议处理: 在处理网络通信时,连接的状态(如建立连接、数据传输、断开连接等)可能会影响如何处理接收到的数据。状态模式可以清晰地表示和转换这些状态,以确保正确处理网络协议。

(3)游戏开发: 在游戏中,角色的行为可能会根据其当前状态(如行走、奔跑、攻击、防御等)而变化。状态模式可以帮助管理这些状态,确保角色在每种状态下都有正确的行为。

(4)订单处理系统: 在电子商务或订单处理系统中,订单的状态(如待支付、已支付、已发货、已完成等)会影响系统如何处理订单。状态模式可以方便地管理这些状态转换和对应的行为。

(5)工作流引擎: 在工作流或业务流程管理系统中,任务或活动的状态(如待处理、处理中、已完成等)决定了下一步应该执行什么操作。状态模式可以帮助管理系统中的状态转换和流程逻辑。

(6)硬件控制: 在嵌入式系统或硬件控制软件中,设备或组件的状态(如开启、关闭、待机等)可能会影响如何发送控制命令。状态模式可以确保在每种状态下都发送正确的命令。

3.1 状态模式应用于用户界面交互

下面的示例展示了一个简单的文本编辑器界面,其中的状态包括“正常”编辑状态和“只读”状态。

首先,定义 State 接口和它的两个实现类 NormalState 和 ReadOnlyState:

#include <iostream>  
#include <memory>  
#include <string>  // State 接口  
class State {
public:virtual void write(const std::string& text) = 0;virtual bool canWrite() const = 0;virtual ~State() = default;
};// 正常编辑状态  
class NormalState : public State {
public:void write(const std::string& text) override {std::cout << "Writing text: " << text << std::endl;}bool canWrite() const override {return true;}
};// 只读状态  
class ReadOnlyState : public State {
public:void write(const std::string& text) override {std::cout << "Cannot write in read-only mode." << std::endl;}bool canWrite() const override {return false;}
};

接下来,定义 TextEditor 类,它持有一个指向 State 对象的 std::unique_ptr:

class TextEditor 
{
public:TextEditor() : currentState(std::make_unique<NormalState>()) {}void setState(std::unique_ptr<State> newState) {currentState = std::move(newState);}void write(const std::string& text) {if (currentState->canWrite()) {currentState->write(text);}}void toggleReadOnly() {if (dynamic_cast<NormalState*>(currentState.get())) {setState(std::make_unique<ReadOnlyState>());}else {setState(std::make_unique<NormalState>());}}private:std::unique_ptr<State> currentState;
};

在 TextEditor 类中,有一个 setState 方法,它接受一个 std::unique_ptr<State>来更新当前状态。write 方法会检查当前状态是否允许写入,如果允许,则调用当前状态的 write 方法。toggleReadOnly 方法用于切换编辑器的读写状态。

最后,在 main 函数中,创建一个 TextEditor 对象,并模拟用户交互:

int main() 
{TextEditor editor;editor.write("This is normal writing."); // 正常写入  editor.toggleReadOnly(); // 切换到只读状态  editor.write("Trying to write in read-only mode."); // 尝试在只读状态下写入  editor.toggleReadOnly(); // 切换回正常状态  editor.write("Back to normal writing."); // 返回正常写入  return 0;
}

上面代码的输出为:

Writing text: This is normal writing.
Writing text: Back to normal writing.

在这个示例中,TextEditor 对象根据当前状态(正常或只读)来允许或拒绝写入操作。通过调用 toggleReadOnly 方法,可以模拟用户切换编辑器的读写状态。由于使用了智能指针 std::unique_ptr,所以不需要手动管理 State 对象的生命周期,它们会在适当的时候被自动删除。这提高了代码的安全性和可维护性。

3.2 状态模式应用网络协议处理

在 C++ 中,状态模式可以很好地应用于网络协议处理,特别是当协议的不同状态需要不同的处理逻辑时。下面是一个简单的示例,展示了如何使用状态模式处理一个简单的网络协议的状态转换。

首先,定义协议的状态接口和几个状态类:

#include <iostream>  
#include <memory>  
#include <string>  // 协议状态接口  
class ProtocolState {
public:virtual void handleData(const std::string& data) = 0;virtual ~ProtocolState() = default;
};// 协议状态A(例如:等待连接)  
class StateA : public ProtocolState {
public:void handleData(const std::string& data) override {std::cout << "StateA: Handling data in state A: " << data << std::endl;// 假设某些条件下转移到状态B  // context->setState(std::make_unique<StateB>());  }
};// 协议状态B(例如:已连接,等待数据)  
class StateB : public ProtocolState {
public:void handleData(const std::string& data) override {std::cout << "StateB: Handling data in state B: " << data << std::endl;// 根据数据内容或其他条件,可能转移到其他状态  }
};

然后,定义一个 ProtocolContext 类,它持有一个指向当前状态对象的智能指针,并暴露处理数据的方法:

class ProtocolContext {
public:ProtocolContext() : currentState(std::make_unique<StateA>()) {}void setData(const std::string& data) {currentState->handleData(data);}void setState(std::unique_ptr<ProtocolState> newState) {currentState = std::move(newState);}private:std::unique_ptr<ProtocolState> currentState;
};

在这个示例中,ProtocolContext 类负责管理当前的状态,并根据接收到的数据调用相应状态的处理方法。状态的转换逻辑可以嵌入到各个状态类的 handleData 方法中,或者根据需要从外部触发。

最后,在 main 函数中,模拟网络协议处理的过程:

int main() 
{ProtocolContext context;// 假设接收到一些数据  context.setData("Some data received");// 根据数据内容或其他条件,可能转移到其他状态  // 例如,如果数据表示连接已建立,可以转移到状态B  // context.setState(std::make_unique<StateB>());  // 再次接收数据,这次会根据当前状态来处理  context.setData("More data received");return 0;
}

上面代码的输出为:

StateA: Handling data in state A: Some data received
StateA: Handling data in state A: More data received

注意:在实际的网络协议处理中,状态的转换可能更加复杂,并且可能涉及网络事件的监听、定时器的使用、错误处理等多种情况。每个状态类可能需要维护自己的内部状态,并根据接收到的数据和网络事件来更新这些状态,以及决定是否需要转移到其他状态。

4 状态模式的优点与缺点

C++ 状态模式的优点主要包括:

(1)封装性: 状态模式通过将特定状态的行为封装在单独的类中,使得代码更加清晰和易于理解。每个状态都有自己专门的处理逻辑,这有助于减少if-else或者switch-case语句的使用,使得代码更加模块化。

(2)扩展性: 如果需要添加新的状态或者修改某个状态的行为,只需要增加新的状态类或者修改现有状态类的实现,而不需要修改使用这些状态的上下文类。这降低了代码之间的耦合度,提高了系统的可维护性和可扩展性。

(3)可维护性: 由于每个状态的行为被封装在单独的类中,所以这些行为可以被独立地测试和验证。此外,状态的改变不会影响到使用这些状态的上下文类,这有助于保持代码的稳定性和可维护性。

(4)状态转换的明确性: 状态模式清晰地表示了状态之间的转换逻辑,使得状态转换的过程更加明确和易于控制。

然而,C++ 状态模式也存在一些缺点:

(1)增加系统复杂性: 对于简单的状态转换,使用状态模式可能会增加系统的复杂性。每个状态都需要一个单独的类,这可能会增加类的数量,使系统变得更加复杂。

(2)性能开销: 由于每次状态转换时都需要创建新的状态对象(除非使用对象池等技术来优化),这可能会带来一定的性能开销。此外,如果状态转换非常频繁,这种开销可能会变得显著。

(3)设计过度: 在某些情况下,过度使用状态模式可能会导致设计过度复杂。如果状态之间的转换逻辑相对简单,或者状态数量很少,那么使用状态模式可能并不是最佳选择。

(4)客户端代码可能变得复杂: 当状态数量较多且转换逻辑复杂时,客户端代码可能需要处理多种状态转换,这可能会使客户端代码变得复杂和难以维护。

相关文章:

  • C语言分析基础排序算法——计数排序
  • 网络建设与运维培训介绍和能力介绍
  • Linux--搭建Zabbix监控系统
  • Vue3:ref和reactive实现响应式数据
  • Java中常用的集合及方法(2)
  • Day36:安全开发-JavaEE应用第三方组件Log4j日志FastJson序列化JNDI注入
  • Java学习笔记NO.18
  • 去除PDF论文行号的完美解决方案
  • 云计算项目十一:构建完整的日志分析平台
  • C++进阶学习
  • AWS使用 Client VPN 配置访问VPC 内网资源
  • android pdf框架-7,白边切割
  • 安卓项目:app注册/登录界面设计
  • 【NR技术】 3GPP支持无人机的关键技术以及场景
  • 《C++游戏编程入门》第2章 真值、分支与游戏循环: Guess My Number
  • Google 是如何开发 Web 框架的
  • $translatePartialLoader加载失败及解决方式
  • 【个人向】《HTTP图解》阅后小结
  • 2017年终总结、随想
  • echarts花样作死的坑
  • Java IO学习笔记一
  • Javascript 原型链
  • java小心机(3)| 浅析finalize()
  • js继承的实现方法
  • KMP算法及优化
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Web标准制定过程
  • 番外篇1:在Windows环境下安装JDK
  • 给新手的新浪微博 SDK 集成教程【一】
  • 前嗅ForeSpider中数据浏览界面介绍
  • 学习Vue.js的五个小例子
  •  一套莫尔斯电报听写、翻译系统
  • FaaS 的简单实践
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • ​queue --- 一个同步的队列类​
  • ​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • #define、const、typedef的差别
  • #if #elif #endif
  • #pragma once与条件编译
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • (4)(4.6) Triducer
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (C#)获取字符编码的类
  • (C语言)输入一个序列,判断是否为奇偶交叉数
  • (非本人原创)史记·柴静列传(r4笔记第65天)
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (译) 理解 Elixir 中的宏 Macro, 第四部分:深入化
  • (转)母版页和相对路径
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • .gitignore
  • .md即markdown文件的基本常用编写语法