A Simple implementation of Observer Design Pattern in Rust

Shobhit chaturvedi
4 min readJun 16, 2023

--

The Observer design pattern is a behavioural pattern that establishes a one-to-many dependency between objects, such that when one object (the subject) changes its state, all dependent objects (the observers) are automatically notified and updated. This pattern promotes loose coupling and enables objects to maintain consistency and synchronisation with the subject’s state changes.

lets understand it from simple diagram seen below —

Observer-Subject relationship

In Observer design pattern —

  1. There are traits subjects (a.k.a. Observables) and Observers.
  2. Observers are register/subscribe to Subject/Topic through a mehtod expose by subject e.g. “register_observer”.
  3. Subject also expose method to deregister/remove observer through method e.g. “remove_observer”.
  4. Subject expose a method e.g. “notify_observers” , this method can be called by actor at any event occurrence or specific condition met.
  5. Observer expose a method e.g. “update” which is called by Subject to notify all Observer about particular event occurred inside system.

let’s understand all above steps by implementing an example in which system/actor wants to notify all security services e.g. Antivirus, Firewall, Anti-spyware, CloudSecurity to trigger security scan in case of any update occurs in some File shared across multiple computers.

lets understand the code —

  1. we created an Observer and Subject trait , any struct implementing Observer trait eligible to register itself for Subject trait , as Observer trait is bound trait for Subject trait.
// Define the Observer trait with an update method
trait Observer {
fn update(&self);
}

// Define the Subject trait with methods to register, remove, and notify observers
trait Subject<'a, T: Observer> {
fn register_observer(&mut self, observer: &'a T);
fn remove_observer(&mut self, observer: &'a T);
fn notify_observers(&self);
}

2. Now we implemented concrete struct to implement Observer and Subject trait.

FileSubject struct implement all traits methods i.e. register_observer, remove_observer and notify_observers with the bound trait Observer.

impl<'a, T: Observer> FileSubject<'a, T> {
pub fn new() -> FileSubject<'a, T> {
FileSubject {
observers: Vec::new(),
}
}
}

impl<'a, T: Observer+PartialEq> Subject<'a, T> for FileSubject<'a, T> {
fn register_observer(&mut self, observer: &'a T) {
self.observers.push(observer);
}

fn remove_observer(&mut self, observer: &'a T) {
// Find the index of the observer in the vector and remove it
let index = self.observers.iter().position(|x| *x == observer).unwrap();
self.observers.remove(index);
}

fn notify_observers(&self) {
// Call the update method on each observer in the vector
for observer in self.observers.iter() {
observer.update();
}
}
}

ObserverProcess struct implement Observer trait method i.e. update

// Define the ObserverProcess struct that implements the Observer trait
// ObserverProcess must derive PartialEq because we will be comparing all attributbutes
// before removing observer

#[derive(PartialEq)]
struct ObserverProcess {
id: u32,
name: String,
}

impl Observer for ObserverProcess {
fn update(&self) {
println!(" ** Observer {}: id = {} **", self.name, self.id);
}
}

3. main method to demo the working of observer pattern

fn main() {
// Create a FileSubject to hold the observers
let mut subject: FileSubject<ObserverProcess> = FileSubject::new();

// Create several ObserverProcess instances
let observer1 = ObserverProcess { id: 1, name: "Antivirus".to_string() };
let observer2 = ObserverProcess { id: 2, name: "Firewall".to_string() };
let observer3 = ObserverProcess { id: 3, name: "Antispyware".to_string() };
let observer4 = ObserverProcess { id: 4, name: "CloudSecurity".to_string() };

// Register the observers to the subject
subject.register_observer(&observer1);
subject.register_observer(&observer2);
subject.register_observer(&observer3);
subject.register_observer(&observer4);

// Notify the observers
subject.notify_observers();

// Remove observer2 from the subject
subject.remove_observer(&observer2);

// Notify the observers again after removing observer2
subject.notify_observers();
}

Here is the full source —

// Define the Observer trait with an update method
trait Observer {
fn update(&self);
}

// Define the Subject trait with methods to register, remove, and notify observers
trait Subject<'a, T: Observer> {
fn register_observer(&mut self, observer: &'a T);
fn remove_observer(&mut self, observer: &'a T);
fn notify_observers(&self);
}

// Define the FileSubject struct that holds a vector of observers
struct FileSubject<'a, T: Observer> {
observers: Vec<&'a T>,
}

impl<'a, T: Observer> FileSubject<'a, T> {
pub fn new() -> FileSubject<'a, T> {
FileSubject {
observers: Vec::new(),
}
}
}

impl<'a, T: Observer+PartialEq> Subject<'a, T> for FileSubject<'a, T> {
fn register_observer(&mut self, observer: &'a T) {
self.observers.push(observer);
}

fn remove_observer(&mut self, observer: &'a T) {
// Find the index of the observer in the vector and remove it
let index = self.observers.iter().position(|x| *x == observer).unwrap();
self.observers.remove(index);
}

fn notify_observers(&self) {
// Call the update method on each observer in the vector
for observer in self.observers.iter() {
observer.update();
}
}
}

// Define the ObserverProcess struct that implements the Observer trait
// ObserverProcess must derive PartialEq because we will be comparing all attributbutes
// before removing observer

#[derive(PartialEq)]
struct ObserverProcess {
id: u32,
name: String,
}

impl Observer for ObserverProcess {
fn update(&self) {
println!(" ** Observer {}: id = {} **", self.name, self.id);
}
}

fn main() {
// Create a FileSubject to hold the observers
let mut subject: FileSubject<ObserverProcess> = FileSubject::new();

// Create several ObserverProcess instances
let observer1 = ObserverProcess { id: 1, name: "Antivirus".to_string() };
let observer2 = ObserverProcess { id: 2, name: "Firewall".to_string() };
let observer3 = ObserverProcess { id: 3, name: "Antispyware".to_string() };
let observer4 = ObserverProcess { id: 4, name: "CloudSecurity".to_string() };

// Register the observers to the subject
subject.register_observer(&observer1);
subject.register_observer(&observer2);
subject.register_observer(&observer3);
subject.register_observer(&observer4);

// Notify the observers
subject.notify_observers();

// Remove observer2 from the subject
subject.remove_observer(&observer2);

// Notify the observers again after removing observer2
subject.notify_observers();
}

--

--