【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern

ops/2024/12/29 20:49:29/

前言

  • (题外话)nav2系列教材,yolov11部署,系统迁移教程我会放到年后一起更新,最近年末手头事情多,还请大家多多谅解。
  • 回顾我们整个学习历程,我们已经学习了很多C++的代码特性,也学习了很多ROS2的跨进程通讯方式,也学会了很多路径规划的种种算法。那么如何将这些学习到的东西整合在一起,合并在工程中,使我们的机器人可以自主进行多任务执行和状态切换呢?本系列教程我们就来看看工程中最常用的几个AI控制结构:

1 史山回顾–if-else 分支结构

1-1 介绍(不需要介绍了吧)
  • if-else 分支结构是一种最基本的控制流语句,用于根据不同的条件执行不同的代码块。
    • 当条件为真时,执行if块中的代码
    • 当条件为假时,执行else块中的代码。
    • 我们也可以使用多个else if来处理多种不同的条件。
  • 语法特点:
if (condition) {// 条件为真时执行的代码块
} else if (another_condition) {// 第二个条件为真时执行的代码块
} else {// 所有条件都不满足时执行的代码块
}
1-2 缺点分析
  • 不适用于复杂决策:当决策的条件较多时,if-else结构会变得冗长且难以维护。例如,多个else if分支会导致代码变得难以管理,增加出错的概率。
  • 可读性差:当嵌套层数增加时,代码的可读性会下降,尤其在多个条件需要组合的情况下。
  • 性能问题:如果条件分支很多,if-else结构的执行效率相对较低。每个条件都需要被评估,可能影响性能。
  • 可扩展低:当系统需要加入新的判断条件时,必须修改现有的if-else结构,这样会导致现有代码变得更加复杂,增加了出错的风险。

1-3 良好代码需要具备的三大特性
  1. 可读性 (Readability):可读性是指代码能够被其他开发者(或未来的自己)轻松理解和维护。可读的代码清晰地表达了程序的意图,使得开发者能够快速理解代码的目的和工作原理,而不需要大量的时间去理解每一行代码。
  • *可读性的具体表现:
    • 命名规范: 变量、函数、类和方法的命名应具有描述性,能够直观反映其用途。例如,calculateTotalAmount()calc() 更具描述性。
    • 清晰的结构: 代码组织良好,易于导航。例如,使用合理的文件夹结构、模块化设计、合适的注释等。
    • 一致性: 项目中的命名、缩进和编码风格保持一致,避免混乱和不必要的复杂性。
    • 注释: 适量的注释帮助解释为什么要做某件事,而非如何做。复杂的算法和逻辑应该附有说明,避免过度注释。

  1. 可维护性 (Maintainability):可维护性是指代码能够随着需求的变化、技术的更新或 bug 的修复而轻松进行修改和扩展。良好的可维护性意味着代码结构清晰,具备良好的组织,使得开发者可以快速定位问题并做出修改。
  • *可维护性的具体表现:
    • 模块化: 代码被合理分割成模块、类和函数,每个模块完成一个明确的功能,减少了代码的耦合性。
    • 高内聚低耦合: 每个模块或函数应该有清晰、单一的职责,模块之间的依赖关系应该尽量减少。
    • 易于扩展: 当需求发生变化时,代码能够方便地进行扩展而不会影响已有的功能。遵循开闭原则(Open/Closed Principle),即“对扩展开放,对修改封闭”。
    • 避免硬编码: 避免将变化的内容写死在代码中(如配置、路径等),而是通过外部配置文件、环境变量等方式管理。

  1. 可扩展性 (Scalability):可扩展性是指代码能够应对未来需求变化,能够在不大规模重构的情况下进行扩展。当系统的功能、用户量、数据量增长时,良好的扩展性保证了系统能够平稳地进行调整和优化。
  • *可扩展性的具体表现:
    • 灵活的架构: 代码设计采用松耦合、清晰的层次结构和抽象层,确保系统可以随着需求的变化进行扩展。
    • 设计模式的应用: 在适当的场景下,使用合适的设计模式(如工厂模式、策略模式等)帮助代码更容易扩展。
    • 性能优化: 在设计时考虑到性能瓶颈,能够支持大规模的数据和请求。例如,避免重复计算、优化数据库查询等。
    • 无缝集成: 可以轻松地与新的技术、第三方库或服务进行集成,支持横向扩展(增加更多服务器)或纵向扩展(增加服务器性能)。

2 状态模式State Pattern

implements
implements
holds
«interface»
State
+handle()
+changeState()
ConcreteStateA
+handle()
+changeState()
ConcreteStateB
+handle()
+changeState()
Context
-State* currentState
+setState(State* newState)
+request()
2-1 介绍
  • 状态模式(State Pattern)是一种行为型设计模式,它允许一个对象在其内部状态发生改变时改变其行为,看起来就像是改变了对象的类。换句话说,状态模式让对象在不同的状态下表现出不同的行为,而无需通过条件判断或复杂的控制逻辑来管理状态的转变。
2-2 关键概念
  • Context(上下文):维护当前的状态对象,并委托行为的执行给当前状态对象。
  • State(状态):定义状态行为的接口。每个具体的状态类实现该接口,并实现相应的状态转换逻辑。
  • ConcreteState(具体状态):每个具体的状态类,定义了不同状态下的具体行为。

3 概念补充

3-1 UML(Unified Modeling Language)概述
  • UML(统一建模语言)是一种标准化的建模语言,用于描述软件系统的结构、行为、交互和功能。它提供了一种通用的图形语言,能够帮助开发人员、架构师和分析师在不同层次上描述复杂的软件系统。
  • UML图有多种类型,常见的包括:
    • 类图(Class Diagram):描述系统中类、接口及其关系(继承、关联等)。
    • 对象图(Object Diagram):展示对象在某一时刻的实例化状态。
    • 用例图(Use Case Diagram):描述系统的功能需求及与外部实体(如用户)的交互。
    • 活动图(Activity Diagram):描述操作流程,类似于流程图。
    • 序列图(Sequence Diagram):描述对象间的交互和消息传递顺序。
    • 状态图(State Diagram):描述对象在生命周期中的状态及状态转换。
    • 组件图(Component Diagram):描述软件的组件结构。
    • 部署图(Deployment Diagram):描述系统的物理部署结构。
  • 在代码中,我们通常会使用类图(Class Diagram)来描述系统中类、接口及其关系(继承、关联等)。
  • 比如说我们来看一段C++的基础接口使用的代码
#include <iostream>
#include <cmath>class Shape {
public:virtual double area() const = 0; // 纯虚函数,要求派生类实现virtual void display() const = 0; // 显示形状信息virtual ~Shape() {}
};class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}double area() const override {return M_PI * radius * radius;}void display() const override {std::cout << "Circle with radius: " << radius << std::endl;}
};class Rectangle : public Shape {
private:double width, height;public:Rectangle(double w, double h) : width(w), height(h) {}double area() const override {return width * height;}void display() const override {std::cout << "Rectangle with width: " << width << " and height: " << height << std::endl;}
};int main() {Shape* shapes[2];shapes[0] = new Circle(5.0);  // 创建一个Circle对象shapes[1] = new Rectangle(4.0, 6.0);  // 创建一个Rectangle对象for (int i = 0; i < 2; ++i) {shapes[i]->display();std::cout << "Area: " << shapes[i]->area() << std::endl;}// 释放内存delete shapes[0];delete shapes[1];return 0;
}
  • 如果使用UML画出上述代码的类图,则有:
Shape
+area()
+display()
Circle
-radius : double
+area()
+display()
Rectangle
-width : double
-height : double
+area()
+display()

3-2 状态图(State Diagram)
  • 状态图(也称为状态机图,或状态转换图)是 UML 的一种重要图类型,用于描述对象或系统在生命周期内的状态变化及其状态之间的转换。它专注于对象在不同状态之间的转移,通常与某个特定对象或类的生命周期相关。
  • 状态图通常包括以下元素:
    • 状态(State):表示对象在某个时间点的特定条件或行为。
    • 初始状态(Initial State):表示状态机的起始状态,用一个实心圆表示。
    • 终止状态(Final State):表示状态机的结束状态,用一个带圈的实心圆表示。
    • 转换(Transition):表示从一个状态到另一个状态的路径,通常通过箭头表示,箭头上标注事件或条件。
    • 事件(Event):触发状态转换的条件或行为。
    • 动作(Action):在状态进入、退出或转换时执行的操作。
  • 状态图的常见用途:
    • 表示对象的生命周期:例如,描述订单在不同处理阶段的状态(如:已创建、处理中、已完成、已取消等)。
    • 描述系统的反应:例如,描述一个TCP连接的不同状态(如:等待连接、建立连接、数据传输、关闭连接等)。
    • 建模复杂的工作流和状态转换:如电梯的状态图,描述电梯在不同楼层之间的状态转换。
  • 示例:简单的状态图
    • 假设我们有一个 电梯(Elevator),它的状态可以是 “停止”“上升”“下降”。根据按钮按下或者电梯到达目标楼层,它会从一个状态转移到另一个状态。
goUp()
goDown()
reachedTop()
reachedBottom()
goDown()
goUp()
Stopped
MovingUp
MovingDown

3 代码实现

  • 基本上所有的状态模式的例子都会牵扯到电梯哈哈哈,那么我们也来看这样一个例子:
3-1 问题分析
  • 假设我们有一个电梯控制系统,其中电梯有三种状态:
    1. 停止(Stopped):电梯处于停止状态,等待指令。
    2. 上升(MovingUp):电梯在上升中。
    3. 下降(MovingDown):电梯在下降中。
  • 电梯根据用户输入的楼层进行相应的状态转换。当电梯达到目标楼层时,它会停止。我们通过状态模式来处理不同状态下的行为。
3-2 状态图
  • 我们画出电梯的状态图。电梯的状态图描述了电梯如何在不同状态之间切换。
goUp()
goDown()
reachedTop()
reachedBottom()
goDown()
goUp()
Stopped
MovingUp
MovingDown
3-3 类图
  • 下面是状态模式的类图,展示了状态类和上下文类之间的关系。
implements
implements
implements
holds
«interface»
ElevatorState
+handle()
StoppedState
+handle()
MovingUpState
+handle()
MovingDownState
+handle()
Elevator
-ElevatorState* currentState
+setState(ElevatorState* state)
+request()

3-4 基础代码实现(不加状态约束)
  • 我们简单实现以下代码:
#include <iostream>// 状态接口类
class ElevatorState {
public:virtual ~ElevatorState() {}virtual void handle() = 0;
};// 具体状态类:停止
class StoppedState : public ElevatorState {
public:void handle() override {std::cout << "Elevator is stopped, waiting for input.\n";}
};// 具体状态类:上升
class MovingUpState : public ElevatorState {
public:void handle() override {std::cout << "Elevator is moving up.\n";}
};// 具体状态类:下降
class MovingDownState : public ElevatorState {
public:void handle() override {std::cout << "Elevator is moving down.\n";}
};// 上下文类:电梯
class Elevator {
private:ElevatorState* currentState;public:Elevator() : currentState(new StoppedState()) {} // 初始状态为停止状态// 设置当前状态void setState(ElevatorState* state) {delete currentState;    // 删除旧的状态对象currentState = state;   // 设置新的状态}// 请求状态的行为void request() {currentState->handle();}~Elevator() {delete currentState; // 删除状态对象,防止内存泄漏}
};// 测试程序
int main() {Elevator elevator;// 初始状态:停止std::cout << "Initial state:\n";elevator.request();// 切换到上升状态elevator.setState(new MovingUpState());std::cout << "\nAfter changing to MovingUpState:\n";elevator.request();// 切换到下降状态elevator.setState(new MovingDownState());std::cout << "\nAfter changing to MovingDownState:\n";elevator.request();// 切换回停止状态elevator.setState(new StoppedState());std::cout << "\nAfter changing back to StoppedState:\n";elevator.request();return 0;
}
  • ElevatorState 是一个抽象类,定义了所有电梯状态必须实现的接口。
    • 其中的 handle() 方法是一个纯虚函数(即没有实现),所有具体状态类都需要提供具体的实现
  • 接下来的三个类分别代表电梯的不同状态,每个具体的状态类都实现了handle()函数
    • StoppedState 表示电梯处于停止状态。
    • MovingUpState 表示电梯正在上升。
    • MovingDownState 表示电梯正在下降。
  • 电梯类(Elevator)作为上下文类来管理当前状态,并根据状态的变化执行相应的行为

  • 通过不断地调用 setState(),电梯的状态发生变化,最后输出相应的状态行为信息。也就是上述状态模式的本质:它允许一个对象在其内部状态发生改变时改变其行为,看起来就像是改变了对象的类
  • 请添加图片描述

3-5 加入转移约束条件
  • 聪明的你一定发现了,上述代码并没有约束状态转换,而是使用了setState()来显式进行状态切换,这是为了初学者能一眼看明白状态模式的本质。
  • 那么接下来,我们可以进一步为上述代码加上状态转换的约束
goUp()
goDown()
reachedTop()
reachedBottom()
goDown()
goUp()
Stopped
MovingUp
MovingDown
  • 首先我们在状态基类中添加所有事件的实现函数
    • 我们让默认的事件函数设置为调用报错,这样当错误的状态转移发生的时候,报错就会发生
#include <iostream>  
#include <stdexcept>  // 状态接口类  
class ElevatorState {  
public:  virtual ~ElevatorState() {}  virtual void handle() = 0;  virtual void goUp() {  throw std::logic_error("Invalid operation for this state");  }  virtual void goDown() {  throw std::logic_error("Invalid operation for this state");  }  virtual void reachedTop() {  throw std::logic_error("Invalid operation for this state");  }  virtual void reachedBottom() {  throw std::logic_error("Invalid operation for this state");  }  
};
  • 紧接着我们根据每个状态下能执行的事件对应的函数实现,比如:
    • 我们只实现了reachedTop()goDown(),也就是当电梯处于上升状态时候,合法的事件是reachedTop()goDown(),当你输入其他的事件时候,就会调用默认的实现,也就是报错throw std::logic_error("Invalid operation for this state");
// 具体状态类:上升  
class MovingUpState : public ElevatorState {  
public:  void handle() override {  std::cout << "Elevator is moving up.\n";  }  void reachedTop() override {  std::cout << "Elevator reached the top. Stopping.\n";  }  void goDown() override {  std::cout << "Switching to moving down state.\n";  }  
};
  • 同时在上下文类中,我们实现一些封装好的函数
// 上下文类:电梯  
class Elevator {  
private:  ElevatorState* currentState;  public:  Elevator() : currentState(new StoppedState()) {request();} // 初始状态为停止状态  // 设置当前状态  void setState(ElevatorState* state) {  delete currentState;    // 删除旧的状态对象  currentState = state;   // 设置新的状态  }  // 请求状态的行为  void request() {  currentState->handle();  }  // 封装的 goUp 操作,既改变状态,又执行操作  void goUp() {  currentState->goUp();  setState(new MovingUpState());  request();  }  // 封装的 goDown 操作,既改变状态,又执行操作  void goDown() {  currentState->goDown();  setState(new MovingDownState());  request();  }  // 电梯到达顶部  void reachedTop() {  currentState->reachedTop();  setState(new StoppedState());  request();  }  // 电梯到达底部  void reachedBottom() {  currentState->reachedBottom();  setState(new StoppedState());  request();  }  ~Elevator() {  delete currentState; // 删除状态对象,防止内存泄漏  }  
};
  • 我们来进行基础的代码测试:
// 测试程序  
int main() {  Elevator elevator;  elevator.goUp();  elevator.goDown();  elevator.reachedBottom();  return 0;  
}
  • 上述操作中
    • goUp():电梯从停止状态切换到上升状态 (MovingUpState)。
    • goDown():电梯在上升状态时,用户又要求电梯下降,电梯会从上升状态切换到下降状态 (MovingDownState)。
    • reachedBottom():电梯在下降状态下,达到底部,触发停止状态。
  • 程序输出:
  • 请添加图片描述
3-6 转移约束条件测试
  • 那我们来测试错误的输入:
// 测试程序  
int main() {  Elevator elevator;  elevator.goUp();  elevator.goUp();  elevator.goDown();  elevator.reachedBottom();  return 0;  
}
  • goUp() 后,电梯应该从 停止状态 切换到 上升状态 (MovingUpState)。
  • goUp(): 当前电梯处于 上升状态 (MovingUpState),调用 goUp() 在上升状态下是不允许的,因此应该抛出异常。这是一个非法操作。
  • 请添加图片描述

3-7 完整代码
#include <iostream>  
#include <stdexcept>  // 状态接口类  
class ElevatorState {  
public:  virtual ~ElevatorState() {}  virtual void handle() = 0;  virtual void goUp() {  throw std::logic_error("Invalid operation for this state");  }  virtual void goDown() {  throw std::logic_error("Invalid operation for this state");  }  virtual void reachedTop() {  throw std::logic_error("Invalid operation for this state");  }  virtual void reachedBottom() {  throw std::logic_error("Invalid operation for this state");  }  
};  // 具体状态类:停止  
class StoppedState : public ElevatorState {  
public:  void handle() override {  std::cout << "Elevator is stopped, waiting for input.\n";  }  void goUp() override {  std::cout << "Elevator is starting to move up.\n";  }  void goDown() override {  std::cout << "Elevator is starting to move down.\n";  }  
};  // 具体状态类:上升  
class MovingUpState : public ElevatorState {  
public:  void handle() override {  std::cout << "Elevator is moving up.\n";  }  void reachedTop() override {  std::cout << "Elevator reached the top. Stopping.\n";  }  void goDown() override {  std::cout << "Switching to moving down state.\n";  }  
};  // 具体状态类:下降  
class MovingDownState : public ElevatorState {  
public:  void handle() override {  std::cout << "Elevator is moving down.\n";  }  void reachedBottom() override {  std::cout << "Elevator reached the bottom. Stopping.\n";  }  void goUp() override {  std::cout << "Switching to moving up state.\n";  }  
};  // 上下文类:电梯  
class Elevator {  
private:  ElevatorState* currentState;  public:  Elevator() : currentState(new StoppedState()) {  request();  } // 初始状态为停止状态  // 设置当前状态  void setState(ElevatorState* state) {  delete currentState;    // 删除旧的状态对象  currentState = state;   // 设置新的状态  }  // 请求状态的行为  void request() {  currentState->handle();  }  // 封装的 goUp 操作,既改变状态,又执行操作  void goUp() {  currentState->goUp();  setState(new MovingUpState());  request();  }  // 封装的 goDown 操作,既改变状态,又执行操作  void goDown() {  currentState->goDown();  setState(new MovingDownState());  request();  }  // 电梯到达顶部  void reachedTop() {  currentState->reachedTop();  setState(new StoppedState());  request();  }  // 电梯到达底部  void reachedBottom() {  currentState->reachedBottom();  setState(new StoppedState());  request();  }  ~Elevator() {  delete currentState; // 删除状态对象,防止内存泄漏  }  
};  // 测试程序  
int main() {  Elevator elevator;  elevator.goUp();  elevator.goDown();  elevator.reachedBottom();  return 0;  
}

4 小结

  • 本节我们学习了如何通过 状态模式(State Pattern) 来有效地管理一个对象的状态转换及其行为,尤其是在复杂的控制系统(如电梯控制系统)中应用。在这里插入图片描述

  • 下一节我们讲讲有限状态机FSM的实现

  • 如有错误,欢迎指出!!!

  • 感谢大家的支持!!!


http://www.ppmy.cn/ops/144759.html

相关文章

分布式系统架构4:容错设计模式

这是小卷对分布式系统架构学习的第4篇文章&#xff0c;虽然知道大家都不喜欢看纯技术文章&#xff0c;写了也没多少阅读量&#xff0c;但是为了个人要成长&#xff0c;小卷最近每天都会更新分布式的文章 1.概念 容错策略&#xff0c;指的是“面对故障&#xff0c;我们该做些什…

什么,不用 Tomcat 也能运行 Java web?

在 Java web 开发领域&#xff0c;传统的 Tomcat 服务器一直占据着重要地位。但如今&#xff0c;Blade 框架的出现为我们提供了一种全新的开发体验&#xff0c;它无需依赖 Tomcat 便可运行 Java web 应用。 一、Blade 框架简介 是一款轻量级且高性能的 Java web 框架。其设计理…

ArcGIS Pro 3.4新功能2:Spatial Analyst新特性,密度、距离、水文、太阳能、表面、区域分析

Spatial Analyst 扩展模块在 ArcGIS Pro 3.4 中引入了新功能和增强功能。此版本为您提供了用于表面和区域分析的新工具以及改进的密度和距离分析功能&#xff0c;多种用于水文分析的工具性能的提高&#xff0c;一些新的太阳能分析功能。 目录 1.密度分析 2.距离分析 3.水文…

websocket 局域网 webrtc 一对一 多对多 视频通话 的示例

基本介绍 WebRTC&#xff08;Web Real-Time Communications&#xff09;是一项实时通讯技术&#xff0c;它允许网络应用或者站点&#xff0c;在不借助中间媒介的情况下&#xff0c;建立浏览器之间点对点&#xff08;Peer-to-Peer&#xff09;的连接&#xff0c;实现视频流和&am…

汉塔上网行为管理 ping.php 远程命令执行漏洞复现(附脚本)

0x01 产品描述: 上海汉塔网络科技有限公司多年来一直专注于网络应用的软件开发,在网络安全、网络协议分析、网络数据流控制等领域有着丰富的经验和雄厚的技术实力。同时,公司积累了丰富的数据通信及网络安全产品研发、生产、销售及服务经验,是行业领先的新一代信息安全产品…

HTTP

目录 1.Http的基本代码 1.1 HttpServer.hpp 1.2 简单测试一下 1.3 用telnet测试一下 1.4 用浏览器访问 1.5 返回相应的过程&#xff08;网页版本&#xff09;​编辑 1.5.1 再次用浏览器访问 1.6 返回相应的过程&#xff08;文件版本&#xff09; 1.6.1网页 1.6.2 测试 …

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证3)

根据参考文献1中JWT Token的组成及计算方式&#xff0c;对照参考文献2中的界面&#xff0c;实现简单的JWT Token解析及验证程序&#xff0c;主要功能包括&#xff1a;   1&#xff09;拆分Token字符串&#xff0c;将前两段使用Base64UrlEncoder类解码并转为UTF8字符串&#x…

【Elasticsearch04】企业级日志分析系统ELK之Elasticsearch 插件

Elasticsearch 插件 通过使用各种插件可以实现对 ES 集群的状态监控, 数据访问, 管理配置等功能 ES集群状态: green 绿色状态:表示集群各节点运行正常&#xff0c;而且没有丢失任何数据&#xff0c;各主分片和副本分片都运行正常 yellow 黄色状态:表示由于某个节点宕机或者其…