How can I implement the observer pattern in Rust?

后端 未结 5 977
轻奢々
轻奢々 2020-12-28 17:01

I have an observable collection and an observer. I want the observer to be a trait implementation of trait Observer. The observable object should be able to not

5条回答
  •  清酒与你
    2020-12-28 17:33

    This is my implementation based on the answers to this question and much pain and suffering. I use a weak reference to store the observer and a RefCell to be able to call a mutable notify().

    I'm using Arc because my listener could be called from any thread. If you were using a single thread, you could use Rc.

    Every time dispatch() is called, it will check if there are any weakly referenced listeners which have disappeared. If there are any it will clean up the listener list.

    pub enum Event {} // You make Event hold anything you want to fire 
    
    pub trait Listener {
        fn notify(&mut self, event: &Event);
    }
    
    pub trait Dispatchable
        where T: Listener
    {
        fn register_listener(&mut self, listener: Arc>);
    }
    
    pub struct Dispatcher
        where T: Listener
    {
        /// A list of synchronous weak refs to listeners
        listeners: Vec>>,
    }
    
    impl Dispatchable for Dispatcher
        where T: Listener
    {
        /// Registers a new listener
        fn register_listener(&mut self, listener: Arc>) {
            self.listeners.push(Arc::downgrade(&listener));
        }
    }
    
    impl Dispatcher
        where T: Listener
    {
        pub fn new() -> Dispatcher {
            Dispatcher { listeners: Vec::new() }
        }
    
        pub fn num_listeners(&self) -> usize {
            self.listeners.len()
        }
    
        pub fn dispatch(&mut self, event: Event) {
            let mut cleanup = false;
            // Call the listeners
            for l in self.listeners.iter() {
                if let Some(mut listener_rc) = l.upgrade() {
                    let mut listener = listener_rc.borrow_mut();
                    listener.notify(&event);
                } else {
                    println!("Cannot get listener, cleanup necessary");
                    cleanup = true;
                }
            }
            // If there were invalid weak refs, clean up the list
            if cleanup {
                println!("Dispatcher is cleaning up weak refs");
                self.listeners.retain(|ref l| {
                    // Only retain valid weak refs
                    let got_ref = l.clone().upgrade();
                    match got_ref {
                        None => false,
                        _ => true,
                    }
                });
            }
        }
    }
    

    Here is a unit test code snippet that exercises it.

    The test is from a card game library where my Event enum has FlopDealt and GameFinished variants. The test creates and registers my listener, and ensures that it was called when FlopDealt is dispatched. The scoped section is so I can test the weak reference behaviour after the listener goes out of scope. I fire another event and count the number of listeners to ensure the list was cleaned out.

    use std::time::Instant;
    
    #[derive(Debug)]
    pub enum Event {
        FlopDealt,
        GameFinished { ended: Instant },
    }
    
    struct MyListener {
        pub flop_dealt: bool,
    }
    
    impl Listener for MyListener {
        fn notify(&mut self, event: &Event) {
            println!("Notify called with {:?}", event);
            if let Event::FlopDealt = event {
                println!("Flop dealt");
                self.flop_dealt = true;
            }
        }
    }
    
    #[test]
    fn events_register() {
        let mut d: Dispatcher = Dispatcher::new();
    
        {
            let listener_rc = Arc::new(RefCell::new(MyListener { flop_dealt: false }));
            d.register_listener(listener_rc.clone());
            d.dispatch(Event::FlopDealt);
    
            let flop_dealt = listener_rc.borrow().flop_dealt;
            println!("Flop was {}dealt", if flop_dealt { "" } else { "not " });
            assert_eq!(flop_dealt, true);
            assert_eq!(d.num_listeners(), 1);
        }
    
        // Listener should disappear
        d.dispatch(Event::GameFinished {
            ended: Instant::now(),
        });
        assert_eq!(d.num_listeners(), 0);
    }
    

提交回复
热议问题