继续我的学习笔记,不同的思路,不同的启发,抛砖引玉。
查表法:
通过数组、元组、迭代器等实现查表法, 下面的状态转换表,是一个数组,每个数组元素是一个三元素元组,分别定义了:当前状态、事件、下一个转态;以:当前状态和事件为key, 可以查询出对应的下一个状态;当然也可以定义为四元素元组,增加一个函数指针或闭包,用于处理对应状态下的事件;状态和事件类型采用了字符串类型别名,为的是简单明了,易于配置, 但效率不高, 列如可以替换成整数编码或enum枚举类型,这样看着紧凑、清晰、效率高一些!
// 状态和事件定义
type State = &'static str;
type Event = &'static str;
// 状态转移表(当前状态, 事件)→ 下一状态
const TRANSITIONS: &[(State, Event, State)] = &[
("Red", "Timer", "Green"),
("Green", "Timer", "Yellow"),
("Yellow", "Timer", "Red"),
];
struct TrafficLight {
current: State,
}
impl TrafficLight {
fn new() -> Self { Self { current: "Red" }
}
fn handle_event(&mut self, event: Event) {
//do action
println!("handle_event: State:{}->Event:{}", self.current, event);
// 查表获取下一状态
self.current = TRANSITIONS
.iter()
.find(|(s, e, _)| *s == self.current && *e == event)
.map(|(_, _, next)| *next)
.unwrap_or(self.current); // 无匹配时保持当前状态
}
}
fn main(){
let mut sm = TrafficLight::new();
sm.handle_event("Timer");
}
总结:这个实现适合状态和事件总数比较少的简单情况,其中通过数组遍历查找下一个状态的方法效率不高, 可以通过HashMap来改善,由于Rust HashMap 本身就支持多元素的元组作为key,无需手动实现什么Trait,但是前提是:元组中的每个元素都分别实现了Eq Trait和Hash Trait; 当然元组的长度是有限制的,小于等于12; 详细详细信息请查阅Rust官方文档:
https://doc.rust-lang.org/std/primitive.tuple.html
https://doc.rust-lang.org/std/collections/struct.HashMap.html
use std::collections::HashMap;
fn main(){
let mut map:HashMap<(i32, &str,bool), &str> = HashMap::new();
map.insert((1, "rust", true), "good language");
map.insert((2, "python", true), "good language");
println!("{:?}", map.get(&(1, "rust", true)));
}
事件驱动型
// 1. 定义事件类型
//此枚举默认为复制语义.
#[derive(Debug, PartialEq)]
enum PlayerEvent {
Play,
Pause,
Stop,
}
// 2. 定义状态类型
//由于有String, 所以此枚举默认为移动语义.
#[derive(Debug, Clone, PartialEq)]
enum PlayerState {
Stopped,
Playing(String), // 关联数据:当前播放曲目
Paused(String), // 关联数据:暂停时的曲目
}
// 3. 状态机主体
struct PlayerStateMachine {
current_state: PlayerState,
}
impl PlayerStateMachine {
fn new() -> Self {
Self {
current_state: PlayerState::Stopped,
}
}
// 接收事件,返回新状态(也可直接修改内部状态)
fn handle_event(&mut self, event: PlayerEvent) -> &PlayerState {
self.current_state = match (&self.current_state, event) {
// 停止状态下,收到Play事件→转为状态Playing(默认曲目)
(PlayerState::Stopped, PlayerEvent::Play) => PlayerState::Playing("默认曲目".to_string()),
// 播放状态下,收到Pause事件→转为状态Paused;收到Stop事件→转为状态Stopped
(PlayerState::Playing(track), PlayerEvent::Pause) => PlayerState::Paused(track.clone()),
(PlayerState::Playing(_), PlayerEvent::Stop) => PlayerState::Stopped,
// 暂停状态下,收到Play事件→转为状态Playing;收到Stop事件→转为状态Sopped
(PlayerState::Paused(track), PlayerEvent::Play) => PlayerState::Playing(track.clone()),
(PlayerState::Paused(_), PlayerEvent::Stop) => PlayerState::Stopped,
// 其他情况保持原状态
//注意此处:由于&mut self, 这个可变引用, 所以state不允许被move出去,也不允许被共享引用,
//由于Rust规则,读写互斥, 所以此处选择clone,避免编译错误。
(state, _) => state.clone(),
};
&self.current_state
}
}
// 测试
fn main() {
let mut player = PlayerStateMachine::new();
println!("当前状态: {:?}", player.current_state); // Stopped
player.handle_event(PlayerEvent::Play);
println!("Play事件: {:?}", player.current_state); // Playing("默认曲目")
player.handle_event(PlayerEvent::Pause);
println!("Pause事件: {:?}", player.current_state); // Paused("默认曲目")
player.handle_event(PlayerEvent::Stop);
println!("Stop事件: {:?}", player.current_state); // Stopped
}
其他实现方法也许还有许多种,列如通过宏定义来减少重复代码,异步状态机,泛型状态机等等,精力有限,点到为止,抛砖引玉。
代码编译环境:
Rust playground stable 2024 1.90
后记:
我是一个普通的c++老码农,Rust语言爱好者,如今已四十不惑,青丝白发生,人谈不上机智,然多年来敏而好学,不敢懈怠, 随处学随处记,笔耕不辍,坚信好脑瓜不如烂笔头!如今赋闲家中,翻腾出来这些粗鄙之文,分享出来,抛砖引玉!不过本人水平有限,许多时候也是不求甚解,匆促行文,故而文中难免存有谬误,望诸君海涵指正!
帮忙点个赞吧!鼓励我坚持写下去!谢谢啦!


