前言
本文我们接着来讲讲博主最近在项目中频繁使用的,也就是广泛运用于
C#
或者Java
的一个常用编程机制(思路)-----委托
和事件
。由于C++
在语言特性上没有像C#
那样直接支持委托
和事件
,因此本文我们借着学习这两个新的机制,学习一下如何在C++
中复刻委托
和事件
。本系列:
上一篇文章我们介绍了
C++
中如何实现委托和事件,并且通过委托和事件实现了观察者模式
。委托
:安全类型的函数指针,我们使用std::function
来编写、事件
:特殊的委托,允许对象向外界通知某些事情的发生,但它只能由事件发布者触发,外部只能通过订阅(绑定)方法来响应事件。
本期本系列下半我们来谈谈:
- 基于C++
委托
和事件
实现 发布-订阅模式` - 模拟ROS2进行
多话题
发布-订阅模式的编写 - 基于C++
委托
和事件
实现状态机
- 基于C++
1 基于C++委托
和事件
实现 发布-订阅模式`
1-1 概念
发布-订阅模式
(Publisher-Subscriber Pattern),是一种常见的行为型设计模式
,也被称为观察者模式的变种
。这种模式通过将事件的发布和接收解耦合,让一个系统中的多个组件之间的交互变得更加灵活,尤其适用于消息传递和事件处理场景。- 发布-订阅模式的主要组成部分:
Publisher(发布者)
:发布消息的实体,通常不直接知道有谁订阅了它的事件。Subscriber(订阅者)
:订阅事件并接收消息的实体。订阅者对某个特定事件感兴趣,会注册自己到事件总线中,等待事件的发生。EventBus(事件总线)
:作为事件的传递通道,负责协调发布者和订阅者之间的消息传递。它可以存储订阅者的回调函数,并在发布者触发事件时,通知所有订阅该事件的订阅者。
1-2 发布-订阅模式与观察者模式的区别
- 一句话概括就是:发布-订阅模式由第三方来管理发布和订阅,观察者模式是直接由发布者管理
特性 | 观察者模式(Observer Pattern) | 发布-订阅模式(Publisher-Subscriber Pattern) |
---|---|---|
模式类型 | 行为型设计模式 | 行为型设计模式 |
耦合度 | 观察者和主题(发布者)之间存在直接依赖关系,观察者直接注册到主题上 | 发布者与订阅者之间解耦,发布者不直接知道谁订阅了它的事件 |
通知机制 | 主题对象(Subject)直接通知其所有观察者(Observers) | 通过事件总线或消息队列通知订阅者,发布者不直接通知订阅者 |
发布者与订阅者的关系 | 发布者(主题)知道所有的观察者(订阅者)并维护它们 | 发布者与订阅者之间没有直接联系,事件总线或消息中介负责事件传递 |
订阅机制 | 观察者通过直接注册到主题上进行订阅 | 订阅者通过事件总线或消息队列进行订阅 |
扩展性 | 扩展性较差,增加新的观察者可能需要修改主题(发布者) | 扩展性强,可以随时增加新的订阅者或事件类型,且无需修改发布者 |
事件传播方式 | 主题触发时直接传递给观察者 | 通过中介(事件总线、消息队列等)分发给订阅者 |
事件流向 | 一对多,主题和所有观察者有直接的联系 | 多对多,发布者与多个订阅者通过事件总线解耦 |
1-3 UML类图
1-4 代码示例:基于C++委托
和事件
实现 发布-订阅模式
- 根据上述的概念,我们首先先来编写事件总线 类,他需要包括两种方法:
- 发布方法:通知所有订阅者
- 订阅方法:添加到订阅队列
- 一个事件,这里我定义回调函数的函数签名为
void(const std::string&)
,并使用std::function
来管理
- 那么我们有:
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
// 定义一个事件总线类
class EventBus {
public:
// 使用 std::function 来存储订阅者的回调函数
using EventCallback = std::function<void(const std::string&)>;
// 订阅事件,传入一个回调函数
void subscribe(EventCallback callback) {
subscribers.push_back(callback);
}
// 发布事件,通知所有订阅者
void publish(const std::string& event) {
std::cout << "Event published: " << event << std::endl;
for (auto& subscriber : subscribers) {
subscriber(event); // 通知每个订阅者
}
}
private:
std::vector<EventCallback> subscribers; // 存储所有订阅者的回调函数
};
- 紧接着我们来编写发布者和订阅者
// 发布者类
class Publisher {
public:
Publisher(EventBus& eventBus) : eventBus(eventBus) {}
// 发布事件
void publishEvent(const std::string& event) {
eventBus.publish(event);
}
private:
EventBus& eventBus; // 引用事件总线
};
// 订阅者类
class Subscriber {
public:
Subscriber(const std::string& name) : name(name) {}
// 当事件发布时,订阅者的回调函数
void onEventReceived(const std::string& event) {
std::cout << name << " received event: " << event << std::endl;
}
private:
std::string name; // 订阅者的名字
};
- 完整代码:
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
// 定义一个事件总线类
class EventBus {
public:
// 使用 std::function 来存储订阅者的回调函数
using EventCallback = std::function<void(const std::string&)>;
// 订阅事件,传入一个回调函数
void subscribe(EventCallback callback) {
subscribers.push_back(callback);
}
// 发布事件,通知所有订阅者
void publish(const std::string& event) {
std::cout << "Event published: " << event << std::endl;
for (auto& subscriber : subscribers) {
subscriber(event); // 通知每个订阅者
}
}
private:
std::vector<EventCallback> subscribers; // 存储所有订阅者的回调函数
};
// 发布者类
class Publisher {
public:
Publisher(EventBus& eventBus) : eventBus(eventBus) {}
// 发布事件
void publishEvent(const std::string& event) {
eventBus.publish(event);
}
private:
EventBus& eventBus; // 引用事件总线
};
// 订阅者类
class Subscriber {
public:
Subscriber(const std::string& name) : name(name) {}
// 当事件发布时,订阅者的回调函数
void onEventReceived(const std::string& event) {
std::cout << name << " received event: " << event << std::endl;
}
private:
std::string name; // 订阅者的名字
};
int main() {
// 创建事件总线
EventBus eventBus;
// 创建发布者,传入事件总线
Publisher publisher(eventBus);
// 创建订阅者
Subscriber subscriber1("Subscriber 1");
Subscriber subscriber2("Subscriber 2");
// 订阅事件
eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber1,std::placeholders::_1));
eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber2,std::placeholders::_1));
// 发布一个事件
publisher.publishEvent("New Article Published");
return 0;
}
- 同样这里我们绑定类内函数使用
std::bind
(上一期讲过了的)
eventBus.subscribe(std::bind(&Subscriber::onEventReceived,&subscriber1,std::placeholders::_1));
- 可以看到,发布者调用总线的发布方法,并不需要知道所有的订阅者就能通知所有订阅者
- 相信聪明的你一定觉得,上述例子仍然没有能充分区分开
观察者模式
和发布订阅模式
- 那么我们来看看
发布订阅模式
的进阶用法:去维护多组发布订阅,并且管理通讯之间的话题。
2 发布-订阅模式进阶–不同话题的发布和订阅
2-1 ROS的发布订阅模型(扩展)
- 接触过
ROS
和ROS2
的朋友一定不陌生,在ROS2
中,发布者和订阅者通过 话题(Topic) 来进行通讯。我们通过创建 Publisher(发布者) 和 Subscriber(订阅者) 节点来实现消息的发布和接收。 - 这里我们来看
ROS2
的一个基础的发布订阅代码: - 发布方
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher() : Node("minimal_publisher")
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
std::chrono::seconds(1),
std::bind(&MinimalPublisher::timer_callback, this)
);
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, ROS2!";
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
- 接收方:
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber() : Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)
);
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "Received: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
- 没有接触过的朋友也不要紧,这里说明一下:
- 发布者(Publisher):在
ROS2
中,发布者通过create_publisher
函数来创建一个发布者,并将其与特定的 话题(topic
)相关联。- 这里发布者注册了一个名为
"topic"
的话题 - 同时这里指定了事件(回调函数)的函数类型为
void(*)(std_msgs::msg::String)
- 这里发布者注册了一个名为
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
- 也就是当发布者进行发布的时候,会通知所有订阅了
"topic"
这个话题的所有订阅者
publisher_->publish(message);
- 订阅者(Subscriber):订阅者通过
create_subscription
函数来创建一个订阅者,并指==定订阅的 话题(topic
)==以及回调函数。在这个例子中,订阅者会接收到来自发布者发布的消息,并通过回调函数打印消息内容。- 同理这里我们订阅方注册了话题为
"topic"
- 并绑定了当发布者进行更新的时候需要执行的事件(回调函数)
topic_callback
- 同理这里我们订阅方注册了话题为
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)
);
- 不难发现,当出现多组多对多不同话题进行通讯的时候,单纯的
观察者
模式就会宕机,为此发布订阅
模式把这个处理逻辑递交给事件管理总线
,在上述例子中也就是rclcpp
内部实现的。
2-2 实现话题-发布订阅模型(进阶):实现发布者类和订阅者类
- 那么同样道理,我们可以之间使用
C++
的委托和事件实现上述功能,同时我们借助模板,仿造类型的发布订阅逻辑
2-2-1 前向声明 EventBus
- 由于后续的
Subscriber
类需要引用EventBus
类型的指针或引用,而编译器需要知道EventBus
类型。所以我们需要先进行前向声明总线类,表面传递的是MsgType
// 订阅者类
template <typename MsgType>
class EventBus;
2-2-2 Publisher
类
- 这次我们先创建发布者类
- 发布者的创建需要指定
事件总线类EventBus
和话题
- 发布者类的发布函数调用
事件总线类EventBus
的发布函数 - 这里我们使用智能指针进行管理
- 关于智能指针可以看之前写的一篇文章 【RAII | 设计模式】C++智能指针,内存管理与设计模式
- 发布者的创建需要指定
// 发布者类
template <typename MsgType>
class Publisher
{
public:
Publisher(std::shared_ptr<EventBus<MsgType>> bus, const std::string &topic)
: eventBus(bus), topic(topic) {}
// 发布消息到事件总线
void publish(const MsgType &message)
{
eventBus->publish(topic, message); // 将消息发布到特定的话题
}
private:
std::shared_ptr<EventBus<MsgType>> eventBus; // 引用事件总线
std::string topic; // 发布者的主题
};
2-2-3 Subscriber
类
- 同样道理我们指定订阅者
Subscriber
是一个模板类,允许我们为任意类型的消息(MsgType
)创建订阅者。- 在构造时,
Subscriber
需要提供一个名字和一个回调函数。回调函数是一个std::function
对象,用来处理接收到的消息。 onMessageReceived
:当事件总线发布消息时,Subscriber
会调用这个函数来执行它的回调函数。
template <typename MsgType>
class Subscriber
{
public:
Subscriber(const std::string &name, std::function<void(const MsgType &)> callback)
: subscriberName(name), callbackFn(callback) {}
// 当接收到消息时调用回调函数
void onMessageReceived(const MsgType &message)
{
callbackFn(message);
}
private:
std::string subscriberName;
std::function<void(const MsgType &)> callbackFn; // 回调函数
};
2-3 实现话题-发布订阅模型(进阶):实现事件总线类EventBus
- 紧接着我们来看最为重要的
事件总线类EventBus
,先放上完整的代码,然后我们详细分析
// 事件总线类
template <typename MsgType>
class EventBus : public std::enable_shared_from_this<EventBus<MsgType>> // 继承 enable_shared_from_this{
public:
using MessageType = MsgType;
using CallbackType = std::function<void(const MessageType &)>;
// 创建订阅者并将其添加到话题订阅者列表中
std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)
{
auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);
subscribers[topic].push_back(subscriber); // 将订阅者添加到该话题的订阅者列表中
return subscriber;
}
// 创建发布者
std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)
{
return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);
}
// 发布消息到指定话题
void publish(const std::string &topic, const MsgType &message)
{
auto it = subscribers.find(topic);
if (it != subscribers.end()) // 检查该话题是否有订阅者
{
for (const auto &subscriber : it->second)
{
subscriber->onMessageReceived(message); // 调用订阅者的回调函数
}
}
else
{
std::cout << "No subscribers for topic: " << topic << std::endl;
}
}
private:
std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers; // 存储话题和其订阅者
};
2-3-1 使用shared_from_this()传递给发布者类
- 我们回顾一下上面发布者类中需要事件类来调用
事件总线类EventBus
的publish方法
// 发布消息到事件总线
void publish(const MsgType &message)
{
eventBus->publish(topic, message); // 将消息发布到特定的话题
}
- 因此在
事件总线类EventBus
中我们需要被本体的智能指针传递给发布者类
// 创建发布者
std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)
{
return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);
}
在智能指针章节我们提到过,继承
std::enable_shared_from_this<EventBus<MsgType>>
允许对象通过shared_from_this()
获得一个指向自己对象的shared_ptr
。发布者类就可以使用
this->shared_from_this()
传递事件总线类EventBus
// 创建发布者
std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)
{
return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);
}
2-3-2 订阅者类创建与事件存储
- 根据我们的定义,我们定义事件为如下
using MessageType = MsgType;
using CallbackType = std::function<void(const MessageType &)>;
- 同时我们维护一个映射函数,用于存储话题和函数的对应关系
std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers; // 存储话题和其订阅者
- 那么每次我们创建订阅者就只需要添加到对应的容器中
// 创建订阅者并将其添加到话题订阅者列表中
std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)
{
auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);
subscribers[topic].push_back(subscriber); // 将订阅者添加到该话题的订阅者列表中
return subscriber;
}
2-3-3 完整代码与输出
- 完整代码如下
#include <iostream>
#include <unordered_map>
#include <vector>
#include <functional>
#include <memory>
#include <string>
// 订阅者类
template <typename MsgType>
class EventBus; // Forward declaration for EventBus
template <typename MsgType>
class Subscriber
{
public:
Subscriber(const std::string &name, std::function<void(const MsgType &)> callback)
: subscriberName(name), callbackFn(callback) {}
// 当接收到消息时调用回调函数
void onMessageReceived(const MsgType &message)
{
callbackFn(message);
}
private:
std::string subscriberName;
std::function<void(const MsgType &)> callbackFn; // 回调函数
};
// 发布者类
template <typename MsgType>
class Publisher
{
public:
Publisher(std::shared_ptr<EventBus<MsgType>> bus, const std::string &topic)
: eventBus(bus), topic(topic) {}
// 发布消息到事件总线
void publish(const MsgType &message)
{
eventBus->publish(topic, message); // 将消息发布到特定的话题
}
private:
std::shared_ptr<EventBus<MsgType>> eventBus; // 引用事件总线
std::string topic; // 发布者的主题
};
// 事件总线类
template <typename MsgType>
class EventBus : public std::enable_shared_from_this<EventBus<MsgType>> // 继承 enable_shared_from_this{
public:
using MessageType = MsgType;
using CallbackType = std::function<void(const MessageType &)>;
// 创建订阅者并将其添加到话题订阅者列表中
std::shared_ptr<Subscriber<MsgType>> create_subscription(const std::string &topic, CallbackType callback)
{
auto subscriber = std::make_shared<Subscriber<MsgType>>(topic, callback);
subscribers[topic].push_back(subscriber); // 将订阅者添加到该话题的订阅者列表中
return subscriber;
}
// 创建发布者
std::shared_ptr<Publisher<MsgType>> create_publisher(const std::string &topic)
{
return std::make_shared<Publisher<MsgType>>(this->shared_from_this(), topic);
}
// 发布消息到指定话题
void publish(const std::string &topic, const MsgType &message)
{
auto it = subscribers.find(topic);
if (it != subscribers.end()) // 检查该话题是否有订阅者
{
for (const auto &subscriber : it->second)
{
subscriber->onMessageReceived(message); // 调用订阅者的回调函数
}
}
else
{
std::cout << "No subscribers for topic: " << topic << std::endl;
}
}
private:
std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber<MsgType>>>> subscribers; // 存储话题和其订阅者
};
// 回调函数
void callback1(const std::string &message)
{
std::cout << "callback1 received message: " << message << std::endl;
}
void callback2(const std::string &message)
{
std::cout << "callback2 received message: " << message << std::endl;
}
int main()
{
auto eventBus = std::make_shared<EventBus<std::string>>();
auto publisher1 = eventBus->create_publisher("/topic1");
auto publisher2 = eventBus->create_publisher("/topic2");
auto subscriber1 = eventBus->create_subscription("/topic1", callback1);
auto subscriber2 = eventBus->create_subscription("/topic2", callback2);
publisher1->publish("Message for topic 1");
publisher2->publish("Message for topic 2");
return 0;
}
- 可以看到对应话题的消息一旦发布以后,订阅方的回调函数就被直接调用了。
3 基于C++委托
和事件
实现状态机
3-1 概念与设计
我们之前在【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(二):从FSM开始的2D游戏角色操控底层源码编写提到过状态机
我们知道
状态机
的基本原理是根据当前的状态和输入的事件,触发状态的转移。那么我们可以这样的把事件和委托的概念运用到者上面:
- 使用 委托(通过 C++ 中的
std::function
)处理状态转换时的行为。 - 使用 事件(即触发某些行为的输入)来引发状态转移。
- 同时我们创建一个状态机 类用于管理当前状态,并根据事件触发状态的转换。
- 使用 委托(通过 C++ 中的
3-2 代码编写
- 我们编写如下状态机类代码:
- 老规矩先上代码然后细说
#include <iostream>
#include <functional>
#include <map>
#include <string>
// 状态机类
class StateMachine {
public:
using State = std::string;
using Event = std::string;
using StateHandler = std::function<void()>;
// 添加状态和相应的处理函数
void AddState(const State& state, StateHandler handler) {
stateHandlers[state] = handler;
}
// 添加事件和相应的状态转换
void AddTransition(const State& fromState, const Event& event, const State& toState) {
transitions[{fromState, event}] = toState;
}
// 触发事件,并根据当前状态和事件进行状态转换
void TriggerEvent(const Event& event) {
auto transitionKey = std::make_pair(currentState, event);
if (transitions.find(transitionKey) != transitions.end()) {
// 转到新的状态
currentState = transitions[transitionKey];
std::cout << "Transitioning to state: " << currentState << std::endl;
// 执行新的状态的处理函数
stateHandlers[currentState]();
} else {
std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
}
}
// 设置初始状态
void SetInitialState(const State& state) {
currentState = state;
}
private:
State currentState;
std::map<State, StateHandler> stateHandlers; // 状态与处理函数的映射
std::map<std::pair<State, Event>, State> transitions; // 状态转换规则
};
3-2-1 核心变量
- 我们先来看最核心的一些定义
public:
using State = std::string;
using Event = std::string;
using StateHandler = std::function<void()>;
private:
State currentState;
std::map<State, StateHandler> stateHandlers; // 状态与处理函数的映射
std::map<std::pair<State, Event>, State> transitions; // 状态转换规则
State
表示状态机中的状态,这里设计为字符串类型(如 “Idle”, “Working”, “Paused”)。Event
表示触发状态变化的事件,这里也设计为字符串类型(如 “Start”, “Pause”, “Resume”, “Stop”)。StateHandler
是一个std::function<void()>
类型,表示每个状态下要执行的具体操作State currentState
: 当前状态。该变量保存状态机的当前状态。std::map<State, StateHandler> stateHandlers
: 这是一个映射表,它将每个状态与一个状态处理函数(StateHandler
)关联。std::map<std::pair<State, Event>, State> transitions
: 这是一个映射表,它将当前状态和触发事件的组合映射到目标状态。每当触发某个事件时,状态机会根据当前状态和事件,查找目标状态并进行状态转移。
3-2-2 函数
// 添加状态和相应的处理函数
void AddState(const State& state, StateHandler handler) {
stateHandlers[state] = handler;
}
// 添加事件和相应的状态转换
void AddTransition(const State& fromState, const Event& event, const State& toState) {
transitions[{fromState, event}] = toState;
}
// 触发事件,并根据当前状态和事件进行状态转换
void TriggerEvent(const Event& event) {
auto transitionKey = std::make_pair(currentState, event);
if (transitions.find(transitionKey) != transitions.end()) {
// 转到新的状态
currentState = transitions[transitionKey];
std::cout << "Transitioning to state: " << currentState << std::endl;
// 执行新的状态的处理函数
stateHandlers[currentState]();
} else {
std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
}
}
// 设置初始状态
void SetInitialState(const State& state) {
currentState = state;
}
AddState
: 向状态机中添加状态和与之关联的处理函数(比如当状态进入时该做什么)。
AddTransition
: 定义一个状态转移规则,指定在某个状态下触发某个事件时,状态机应该转到哪个新状态。TriggerEvent
: 根据当前状态和触发的事件,检查是否有相应的状态转移规则,如果有,则转换到目标状态,并执行目标状态的处理函数。
3-2-3 整体逻辑:
- 初始状态通过
SetInitialState
设置。
- 当调用
TriggerEvent
时,状态机会根据当前状态和触发的事件,查找transitions
中的对应转移规则。 - 如果找到了转移规则,状态机会转换到目标状态,并执行该状态的处理函数(例如打印 “Currently in Working state”)。
3-3 测试
- 我们来进行下述状态和事件的测试
- 我们假设有一个游戏:
- State有 “Idle”, “Working”, “Paused”
- Event有Start", “Pause”, “Resume”, “Stop”
- 同时我们有下述转移表:
- 同时我们给每个状态分配好各自对应需要触发的函数
// 状态机的各个状态处理函数
void IdleState() {
std::cout << "Currently in Idle state." << std::endl;
}
void WorkingState() {
std::cout << "Currently in Working state." << std::endl;
}
void PausedState() {
std::cout << "Currently in Paused state." << std::endl;
}
- 那么我们库这样进行构建:
int main() {
StateMachine sm;
// 添加状态和状态处理函数
sm.AddState("Idle", IdleState);
sm.AddState("Working", WorkingState);
sm.AddState("Paused", PausedState);
// 设置初始状态
sm.SetInitialState("Idle");
// 添加状态转换规则
sm.AddTransition("Idle", "Start", "Working");
sm.AddTransition("Working", "Pause", "Paused");
sm.AddTransition("Paused", "Resume", "Working");
sm.AddTransition("Working", "Stop", "Idle");
// 触发事件
sm.TriggerEvent("Start"); // 转到 Working 状态
sm.TriggerEvent("Pause"); // 转到 Paused 状态
sm.TriggerEvent("Resume"); // 转到 Working 状态
sm.TriggerEvent("Stop"); // 转到 Idle 状态
return 0;
}
3-3-1 完整代码如下
#include <iostream>
#include <functional>
#include <map>
#include <string>
// 状态机类
class StateMachine {
public:
using State = std::string;
using Event = std::string;
using StateHandler = std::function<void()>;
// 添加状态和相应的处理函数
void AddState(const State& state, StateHandler handler) {
stateHandlers[state] = handler;
}
// 添加事件和相应的状态转换
void AddTransition(const State& fromState, const Event& event, const State& toState) {
transitions[{fromState, event}] = toState;
}
// 触发事件,并根据当前状态和事件进行状态转换
void TriggerEvent(const Event& event) {
auto transitionKey = std::make_pair(currentState, event);
if (transitions.find(transitionKey) != transitions.end()) {
// 转到新的状态
currentState = transitions[transitionKey];
std::cout << "Transitioning to state: " << currentState << std::endl;
// 执行新的状态的处理函数
stateHandlers[currentState]();
} else {
std::cout << "No transition available for event: " << event << " from state: " << currentState << std::endl;
}
}
// 设置初始状态
void SetInitialState(const State& state) {
currentState = state;
}
private:
State currentState;
std::map<State, StateHandler> stateHandlers; // 状态与处理函数的映射
std::map<std::pair<State, Event>, State> transitions; // 状态转换规则
};
// 状态机的各个状态处理函数
void IdleState() {
std::cout << "Currently in Idle state." << std::endl;
}
void WorkingState() {
std::cout << "Currently in Working state." << std::endl;
}
void PausedState() {
std::cout << "Currently in Paused state." << std::endl;
}
int main() {
StateMachine sm;
// 添加状态和状态处理函数
sm.AddState("Idle", IdleState);
sm.AddState("Working", WorkingState);
sm.AddState("Paused", PausedState);
// 设置初始状态
sm.SetInitialState("Idle");
// 添加状态转换规则
sm.AddTransition("Idle", "Start", "Working");
sm.AddTransition("Working", "Pause", "Paused");
sm.AddTransition("Paused", "Resume", "Working");
sm.AddTransition("Working", "Stop", "Idle");
// 触发事件
sm.TriggerEvent("Start"); // 转到 Working 状态
sm.TriggerEvent("Pause"); // 转到 Paused 状态
sm.TriggerEvent("Resume"); // 转到 Working 状态
sm.TriggerEvent("Stop"); // 转到 Idle 状态
return 0;
}
- 可以观察到,通过事件和委托可以很方便地管理不同的状态和状态之间的转移,同时让状态机的行为更加清晰和直观。
4 总结
- 本文我们 基于C++
委托
和事件
实现多话题的发布-订阅
模式,同时也实现了 基于委托和事件实现状态机。 - 如有错误,欢迎指出!!!
- 感谢大家的支持!!!