Skip to content

Commit 5f105c8

Browse files
committed
feat: Observer pattern made dynamic + tests
1 parent 51d547f commit 5f105c8

File tree

6 files changed

+295
-143
lines changed

6 files changed

+295
-143
lines changed

patterns/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
mod proxy;
22
mod singleton;
3+
#[allow(dead_code)]
34
mod observer;

patterns/src/observer/editor.rs

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::observer::events::EditorEvent;
2+
use crate::observer::subject::Subject;
3+
4+
pub struct Editor {
5+
listeners: Subject,
6+
mentions: i32,
7+
comments: i32,
8+
}
9+
10+
impl Editor {
11+
pub fn new() -> Editor {
12+
Editor {
13+
listeners: Subject::default(),
14+
mentions: 0,
15+
comments: 0,
16+
}
17+
}
18+
19+
pub fn listeners(&mut self) -> &mut Subject {
20+
&mut self.listeners
21+
}
22+
23+
pub fn comments(&self) -> i32 {
24+
self.comments
25+
}
26+
27+
pub fn mentions(&self) -> i32 {
28+
self.mentions
29+
}
30+
31+
pub fn mention(&mut self, person: String) {
32+
// do stuff internally as an editor
33+
self.mentions += 1;
34+
35+
// do event dispatching
36+
let event = EditorEvent::Mention {
37+
person: person.clone(),
38+
};
39+
self.listeners.notify(&event);
40+
}
41+
42+
pub fn comment(&mut self, person: String, comment: String) {
43+
// do stuff internally as an editor
44+
self.comments += 1;
45+
46+
// do event dispatching
47+
let event = EditorEvent::Comment {
48+
person: person.clone(),
49+
comment: comment.clone(),
50+
};
51+
self.listeners.notify(&event);
52+
}
53+
}
54+
55+
#[cfg(test)]
56+
mod tests {
57+
use super::*;
58+
use crate::observer::email::EmailAlerts;
59+
use crate::observer::events::ListenerCell;
60+
61+
#[test]
62+
fn basic_test() {
63+
// a publisher that emits the events
64+
let mut editor = Editor::new();
65+
66+
// a subscriber that subscribes to the publisher
67+
let alerts = EmailAlerts::new();
68+
let alerts = ListenerCell::from(alerts);
69+
editor.listeners().add_listener(&alerts);
70+
71+
// update the state of the editor
72+
for _ in 0..5 {
73+
editor.comment(String::from("John"), String::from("Some comment"));
74+
editor.mention(String::from("Alice"));
75+
}
76+
77+
// confirm that the subscriber received the events
78+
let editor_count = editor.comments() + editor.mentions();
79+
assert_eq!(alerts.as_ref().count(), editor_count);
80+
81+
// remove the listener
82+
editor.listeners().remove_listener(&alerts);
83+
84+
for _ in 0..10 {
85+
editor.mention(String::from("John"))
86+
}
87+
88+
// confirm that the subscriber didn't receive the events after removal
89+
assert_eq!(alerts.as_ref().count(), editor_count);
90+
assert_ne!(
91+
alerts.as_ref().count(),
92+
editor.comments() + editor.mentions()
93+
);
94+
}
95+
}

patterns/src/observer/email.rs

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use crate::observer::events::{EditorEvent, ListenerUpdate};
2+
use std::cell::Cell;
3+
4+
pub struct EmailAlerts {
5+
count: Cell<i32>,
6+
}
7+
8+
impl EmailAlerts {
9+
pub fn new() -> EmailAlerts {
10+
EmailAlerts {
11+
count: Cell::new(0),
12+
}
13+
}
14+
15+
pub fn count(&self) -> i32 {
16+
self.count.get()
17+
}
18+
19+
pub fn reset(&mut self) {
20+
self.count.set(0);
21+
}
22+
}
23+
24+
impl ListenerUpdate for EmailAlerts {
25+
fn update(&mut self, event: &EditorEvent) {
26+
let count = self.count.get();
27+
self.count.set(count + 1);
28+
match event {
29+
EditorEvent::Mention { person } => {
30+
eprintln!("{person} mentioned you!");
31+
}
32+
EditorEvent::Comment { person, .. } => {
33+
eprintln!("{person} commented on your project.");
34+
}
35+
}
36+
}
37+
}

patterns/src/observer/events.rs

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use std::cell::{Ref, RefCell, RefMut};
2+
use std::rc::{Rc, Weak};
3+
4+
/// Events that can be emitted represented using an enum
5+
pub enum EditorEvent {
6+
Mention { person: String },
7+
Comment { person: String, comment: String },
8+
}
9+
10+
/// Interface for a listener
11+
pub trait ListenerUpdate {
12+
fn update(&mut self, event: &EditorEvent);
13+
}
14+
15+
/// `Listener` wraps the updater `EventListener` and gives out references.
16+
pub struct ListenerCell<T> {
17+
rc: Rc<RefCell<T>>,
18+
}
19+
20+
impl<T> ListenerCell<T>
21+
where
22+
T: ListenerUpdate + 'static,
23+
{
24+
pub fn from(listener: T) -> Self {
25+
ListenerCell {
26+
rc: Rc::new(RefCell::new(listener)),
27+
}
28+
}
29+
30+
/// This is used by `Subject` to obtain the listener.
31+
pub fn get_ref(&self) -> ListenerRef {
32+
let rc: Rc<RefCell<dyn ListenerUpdate>> = self.rc.clone();
33+
ListenerRef {
34+
weak: Rc::downgrade(&rc),
35+
}
36+
}
37+
38+
pub fn as_ref(&self) -> Ref<'_, T> {
39+
self.rc.borrow()
40+
}
41+
42+
pub fn as_mut(&mut self) -> RefMut<'_, T> {
43+
self.rc.borrow_mut()
44+
}
45+
}
46+
47+
impl<T> Clone for ListenerCell<T> {
48+
fn clone(&self) -> Self {
49+
ListenerCell {
50+
rc: self.rc.clone(),
51+
}
52+
}
53+
}
54+
55+
#[derive(Eq)]
56+
pub struct ListenerRef {
57+
weak: Weak<RefCell<dyn ListenerUpdate>>,
58+
}
59+
60+
pub enum ListenerError {
61+
Invalid,
62+
}
63+
64+
impl ListenerRef {
65+
#[inline]
66+
pub fn is_valid(&self) -> bool {
67+
self.weak.upgrade().is_some()
68+
}
69+
70+
pub fn try_update(&mut self, event: &EditorEvent) -> Result<(), ListenerError> {
71+
if let Some(rc) = self.weak.upgrade() {
72+
rc.borrow_mut().update(event);
73+
Ok(())
74+
} else {
75+
Err(ListenerError::Invalid)
76+
}
77+
}
78+
79+
pub fn update(&mut self, event: &EditorEvent) {
80+
match self.try_update(event) {
81+
Ok(_) => (),
82+
Err(ListenerError::Invalid) => panic!("Listener doesn't exist anymore"),
83+
}
84+
}
85+
}
86+
87+
impl PartialEq for ListenerRef {
88+
fn eq(&self, other: &Self) -> bool {
89+
self.weak.ptr_eq(&other.weak)
90+
}
91+
}
92+
93+
#[cfg(test)]
94+
mod tests {
95+
use crate::observer::email::EmailAlerts;
96+
use crate::observer::subject::Subject;
97+
98+
use super::*;
99+
100+
#[test]
101+
fn listener_drop() {
102+
let listener = EmailAlerts::new();
103+
let mut subject = Subject::new();
104+
{
105+
let listener = ListenerCell::from(listener);
106+
assert_eq!(Rc::weak_count(&listener.rc), 0);
107+
assert_eq!(Rc::strong_count(&listener.rc), 1);
108+
subject.add_listener(&listener);
109+
subject.add_listener(&listener);
110+
assert_eq!(subject.listener_count(), 2);
111+
assert_eq!(Rc::weak_count(&listener.rc), 2);
112+
assert_eq!(Rc::strong_count(&listener.rc), 1);
113+
}
114+
assert_eq!(subject.listener_count(), 0);
115+
}
116+
}

0 commit comments

Comments
 (0)