绕过借来的自我的优选模式"检查

时间:2017-04-28 16:19:43

标签: oop design-patterns rust

考虑一种模式,其中有几个状态向调度程序注册,并且每个状态知道在收到适当事件时要转换到哪个状态。这是一个简单的状态转换模式。

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,但我不确定这是接近它的最佳方法。< / p>

以下是我可以想到的每种方法的缺点:

  1. 像我一样把它放进Option:每次我需要的时候都感觉很烦 检查Option总是Some的不变量 panic!或使其成为if let Some() =的NOP并忽略 else子句,但这会导致代码膨胀。做unwrap 或者用if let Some()膨胀代码感觉有点不对。
  2. 将其置于共享所有权Rc<RefCell<>>中:需要堆分配 所有这些变量或构造另一个名为Inner或的结构 具有所有这些不可克隆类型的东西并将其放入 Rc<RefCell<>>
  3. 将内容传回Dispatcher,表明它基本上已删除我们 从地图中移出我们的东西到下一个State 也将通过我们的返回值表示:耦合太多, 打破OOP,不会缩放,因为Dispatcher需要知道所有的 State并需要经常更新。我不认为这是好事 范式,但可能是错的。
  4. 为上面的MOF实施Default:现在我们可以mem::replace 移出旧值时的默认值。恐慌的负担OR 返回错误或执行NOP现在隐藏在执行中 MOF。这里的问题是我们始终无法访问MOF 类型和我们做的那些,它再次臃肿 从用户代码到MOF代码。
  5. 让函数handle_eventself移动fn handle_event(mut self, ...) -> Option<Self>:现在取代Rc<RefCell<>>而不是Box<State>,每次都需要移出Some在调度员中,如果返回Weak<RefCell<>>,则将其放回去。这几乎感觉就像一个大锤,并使许多其他成语不可能,例如,如果我想在一些注册的闭包/回调中进一步分享自我,我通常会先放private void setGalleryBtn() { if (PermissionHandler.checkPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)) { AppHelper.LogCat("Read data permission already granted."); new PickerBuilder(getActivity(), PickerBuilder.SELECT_FROM_GALLERY) .setOnImageReceivedListener(imageUri -> { Intent data = new Intent(); data.setData(imageUri); AppHelper.LogCat("new image SELECT_FROM_GALLERY" + imageUri); mEditProfilePresenter.onActivityResult(this, AppConst.SELECT_PROFILE_PICTURE, RESULT_OK, data); }) .setImageName(getActivity().getString(R.string.app_name)) .setImageFolderName(getActivity().getString(R.string.app_name)) .setCropScreenColor(R.color.colorPrimary) .withTimeStamp(false) .setOnPermissionRefusedListener(() -> { PermissionHandler.requestPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE); }) .start(); } else { AppHelper.LogCat("Please request Read data permission."); PermissionHandler.requestPermission(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE); } } private void setCameraBtn() { if (PermissionHandler.checkPermission(getActivity(), Manifest.permission.CAMERA)) { AppHelper.LogCat("camera permission already granted."); new PickerBuilder(getActivity(), PickerBuilder.SELECT_FROM_CAMERA) .setOnImageReceivedListener(imageUri -> { AppHelper.LogCat("new image SELECT_FROM_CAMERA " + imageUri); Intent data = new Intent(); data.setData(imageUri); mEditProfilePresenter.onActivityResult(this, AppConst.SELECT_PROFILE_CAMERA, RESULT_OK, data); }) .setImageName(getActivity().getString(R.string.app_name)) .setImageFolderName(getActivity().getString(R.string.app_name)) .setCropScreenColor(R.color.colorPrimary) .withTimeStamp(false) .setOnPermissionRefusedListener(() -> { PermissionHandler.requestPermission(getActivity(), Manifest.permission.CAMERA); }) .start(); } else { AppHelper.LogCat("Please request camera permission."); PermissionHandler.requestPermission(getActivity(), Manifest.permission.CAMERA); } } 但现在在回调中共享自我等是不可能的。< / LI>

    还有其他选择吗?有没有被认为是&#34; 最惯用的 &#34;在Rust中这样做的方法?

1 个答案:

答案 0 :(得分:1)

  
      
  1. 让函数handle_event自动移动为fn handle_event(mut self, ...) -> Option<Self>:现在代替Rc<RefCell<>>,您需要Box<State>并在调度程序中每次移出它,如果返回的话有些人把它放回去了。
  2.   

这就是我要做的。但是,如果只有一个强引用,则无需从Rc切换到BoxRc::try_unwrap可以移出Rc

以下是您可能如何重写Dispatcher

的部分内容
struct Dispatcher {
    states: HashMap<Uid, Rc<State>>,
}
impl Dispatcher {
    fn dispatch(&mut self, state_id: Uid, event: Event) {
        if let Some(state_ref) = self.states.remove(&state_id) {
            let state = state_ref.try_unwrap()
                .expect("Unique strong reference required");
            if let Some(next_state) = state.handle_event(event) {
                self.states.insert(state_id, next_state);
            }
        } else {
            // handle state_id not found
        }
    }
}

(注意:dispatch按值获取state_id。在原始版本中,这不是必需的 - 它可能已被更改为通过引用传递。在此版本中,它是必要的,自从state_id传递给HashMap::insert后,看起来Uid似乎是Copy,所以它没什么区别。)

目前尚不清楚state_id是否真的需要成为实现State的结构的成员,因为handle_event内部不需要它 - 所有插入和删除发生在impl Dispatcher内部,这有意义并减少StateDispatcher之间的耦合。

impl State for S0 {
    fn handle_event(self, event: Event) -> Option<Rc<State>> {
        if event == Event::SomeEvent {
            // Do some work
            let next_state = Rc::new(S0 {
                state_id: self.state_id,
                move_only_field: self.mof,
            });
            Some(next_state)
        } else {
            // Do some other work
        }
    }
}

现在你不必处理一个奇怪的,应该是不可能的角落情况,其中Option是None。

  

这几乎感觉就像一个大锤,并且使许多其他成语变得不可能,例如,如果我想在一些已注册的闭包/回调中进一步分享自我,我通常会先放Weak<RefCell<>>但现在在回调中共享自我等等不可能的。

因为如果你有唯一的强引用,你可以离开Rc,你不必牺牲这种技巧。

“感觉像一把大锤”可能是主观的,但对我来说,像fn handle_event(mut self, ...) -> Option<Self>这样的签名所做的是编码一个不变量。对于原始版本,每个impl State for ...必须知道何时插入调度程序并将其从调度程序中删除,以及是否执行此操作是不可检查的。例如,如果在您忘记调用dispatcher.insert(state_id, next_state)的逻辑深处,状态机将不会转换,可能会卡住或更糟。当handle_eventself个值时,这是不可能的 - 你返回下一个状态,或者代码根本不会编译。

(旁白:每次调用dispatch时,原始版本和我的至少两个散列表查找:一次获取当前状态,再次插入新状态。如果你想摆脱在第二次查找时,您可以组合方法:在Option<Rc<State>>中存储HashMap,从take存储Option,而不是将其从地图中完全删除。)