考虑一种模式,其中有多个状态在调度程序中注册,并且每个状态都知道在接收到适当事件时要转换到哪个状态。这是一种简单的状态转换模式。
struct Dispatcher {
states: HashMap<Uid, Rc<RefCell<State>>>,
}
impl Dispatcher {
pub fn insert_state(&mut self, state_id: Uid, state: Rc<RefCell<State>>) -> Option<Rc<RefCell<State>>> {
self.states.insert(state_id, state)
}
fn dispatch(&mut self, state_id: Uid, event: Event) {
if let Some(mut state) = states.get_mut(&state_id).cloned() {
state.handle_event(self, event);
}
}
}
trait State {
fn handle_event(&mut self, &mut Dispatcher, Event);
}
struct S0 {
state_id: Uid,
move_only_field: Option<MOF>,
// This is pattern that concerns me.
}
impl State for S0 {
fn handle_event(&mut self, dispatcher: &mut Dispatcher, event: Event) {
if event == Event::SomeEvent {
// Do some work
if let Some(mof) = self.mof.take() {
let next_state = Rc::new(RefCell::new(S0 {
state_id: self.state_id,
move_only_field: mof,
}));
let _ = dispatcher.insert(self.state_id, next_state);
} else {
// log an error: BUGGY Logic somewhere
let _ = dispatcher.remove_state(&self.state_id);
}
} else {
// Do some other work, maybe transition to State S2 etc.
}
}
}
struct S1 {
state_id: Uid,
move_only_field: MOF,
}
impl State for S1 {
fn handle_event(&mut self, dispatcher: &mut Dispatcher, event: Event) {
// Do some work, maybe transition to State S2/S3/S4 etc.
}
}
关于上面内联评论所说的:
// This is pattern that concerns me.
S0::move_only_field
在这个模式中需要是一个Option
,因为self
在handle_event
中被借用,但我不确定这是否是最好的方法。
以下是我能想到的方法及其缺点:
- 像我所做的那样将其放入
Option
中:这感觉很鬼畜,每次都需要检查不变量,即Option
始终是Some
,否则就会panic!
或者使用if let Some() =
并忽略else子句,但这会导致代码膨胀。使用unwrap
或用if let Some()
让人感到有些不舒服。 - 将其放入共享所有权的
Rc<RefCell<>>
中:需要堆分配所有这样的变量或构造另一个名为Inner
的结构体,它具有所有这些非可克隆类型,并将其放入Rc<RefCell<>>
中。 - 将东西传回
Dispatcher
,表示它基本上要将我们从地图中删除,然后将东西移出我们到下一个State
,这也将通过我们的返回值指示:耦合过多,破坏面向对象编程,不适用于规模较大的项目,因为Dispatcher
需要了解所有的State
并需要频繁更新。我认为这不是一种好的编程范式,但可能是错的。 - 为上述MOF实现
Default
:现在我们可以使用默认值mem::replace
替换它,同时移出旧值。抛出异常、返回错误或执行NOP的负担现在隐藏在MOF
的实现中。问题在于,我们并不总是有访问MOF类型的权限,对于那些我们有访问权限的类型,它又将代码膨胀从用户代码转移到MOF的代码中。 - 让函数
handle_event
通过移动self
来获取self
,如fn handle_event(mut self, ...) -> Option<Self>
:现在,你需要使用Box<State>
,每次在调度程序中移动它,如果返回值是Some
,则将其放回。这几乎感觉像一个大锤,使许多其他惯用语不可能,例如,如果我想在某个注册的闭包/回调中进一步共享self
,我通常会先放置一个Weak<RefCell<>>
,但现在在回调等中共享self
是不可能的。
还有其他选择吗?在Rust中,是否有被认为是“最惯用”的方法?
Uid
的状态机,并且调度程序选择其中一个来处理每个事件。为什么要在HashMap
中保留所有“已使用”的状态 - 状态是否会再次进入?如何进入? - trentunsafe
代码,那么哪些安全性已经被破坏了?另外,您建议如何解决这个问题? - ustulationRc<RefCell>
实现状态转换模式在单线程事件驱动机制中很常见。那么你更喜欢哪种方法(或者如果没有的话,你会用什么来替代这种做法)? - ustulationremove_state
,这被认为是与已编码的insert_state
互补的。 - ustulation