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

news/2024/12/25 10:22:18 标签: c++, 状态模式, 决策树

前言

  • (题外话)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.niftyadmin.cn/n/5798981.html

相关文章

五十一:HPACK如何减少HTTP头部的大小?

在现代的Web通信中&#xff0c;HTTP是最常用的协议。然而&#xff0c;随着网络应用程序的复杂化&#xff0c;HTTP头部的大小迅速增加&#xff0c;尤其是在HTTP/2中&#xff0c;由于其多路复用特性&#xff0c;多个请求和响应共享同一个连接&#xff0c;头部大小对性能的影响变得…

突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除

GitLab停止为中国大陆、香港和澳门地区提供服务&#xff0c;要求用户在60天内迁移账号&#xff0c;否则将被删除。这一事件即将引起广泛的关注和讨论。以下是对该事件的扩展信息&#xff1a; 1. 背景介绍&#xff1a;GitLab是一家全球知名的软件开发平台&#xff0c;提供代码托…

物理层知识要点

文章目录 物理层接口的四大特性通信基础编码和调制&#xff08;1&#xff09;数字数据编码为数字信号&#xff08;2&#xff09;模拟数据编码为数字信号&#xff08;3&#xff09;常见调制方式&#xff08;3&#xff09;信道的极限容量 多路复用技术数据传输方式物理层下的传输…

leetcode 05 回文字符串

leetcode 05 回文字符串 1. 描述 给你一个字符串&#xff0c;找到里面最长的回文字符串 2. 事例 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba" 同样是符合题意的答案。示例 2&#xff1a; …

美国加州房价数据分析01

1.项目简介 本数据分析项目目的是分析美国加州房价数据&#xff0c;预测房价中值。 环境要求&#xff1a; ancondajupyter notebookpython3.10.10 虚拟环境&#xff1a; pandas 2.1.1 numpy 1.26.1 matplotlib 3.8.0 scikit-learn1.3.1 2. 导入并探索数据集 通用的数据分析…

Springboot jar包加密加固并进行机器绑定

获取机器码&#xff0c;通过classfinal-fatjar-1.2.1.jar来获取机器码 命令&#xff1a;java -jar classfinal-fatjar-1.2.1.jar -C 对springboot打包的jar进行加密功能 java -jar classfinal-fatjar-1.2.1.jar -file lakers-ljxny-3.0.0.jar -packages com.lygmanager.laker…

如何用gpt来分析链接里面的内容(比如分析论文链接)和分析包含多个文件中的一块代码

如何用gpt来分析链接里面的内容&#xff0c;方法如下 这里使用gpt4里面有一个网路的功能 点击搜索框下面这个地球的形状即可启动搜索网页模式 然后即可提出问题在搜索框里&#xff1a;发现正确识别和分析了链接里面的内容 链接如下&#xff1a;https://arxiv.org/pdf/2009.1…

B树的实现

B树&#xff08;B-tree&#xff09;是一种平衡的多路查找树&#xff0c;用于存储和管理大量有序数据。与二叉搜索树不同&#xff0c;B树可以在节点中存储多个关键字&#xff0c;并拥有多个子节点&#xff0c;使得查找、插入和删除操作具有更高的效率。 B树的基本定义&#xff…