Last active
February 3, 2023 13:55
-
-
Save th3james/4ebad186cb17912913b6813581465dfe to your computer and use it in GitHub Desktop.
Two Rust Dependency Injection patterns: one utilising compile-time generics, the other the runtime `dyn` keyword.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// An attempt to have a struct with dependency which is a trait | |
// with a default constructor with provides the production dependency | |
// This approach uses `dyn` keyword which means the traits are checked at runtime | |
// | |
// Adapted from https://doc.rust-lang.org/book/ch15-05-interior-mutability.html | |
pub trait Messenger { | |
fn send(&self, msg: &str); | |
} | |
pub struct ConcreteMessenger; | |
impl Messenger for ConcreteMessenger { | |
fn send(&self, msg: &str) { | |
println!("{}", msg); | |
} | |
} | |
pub struct LimitTracker<'a> { | |
messenger: & 'a dyn Messenger, | |
value: usize, | |
max: usize, | |
} | |
impl<'a> LimitTracker<'a> | |
{ | |
pub fn new(max: usize) -> LimitTracker<'a> { | |
LimitTracker { | |
messenger: &ConcreteMessenger, | |
value: 0, | |
max, | |
} | |
} | |
pub fn new_with_messenger(messenger: &'a dyn Messenger, max: usize) -> LimitTracker<'a> { | |
LimitTracker { | |
messenger, | |
value: 0, | |
max, | |
} | |
} | |
pub fn set_value(&mut self, value: usize) { | |
self.value = value; | |
let percentage_of_max = self.value as f64 / self.max as f64; | |
if percentage_of_max >= 1.0 { | |
self.messenger.send("Error: You are over your quota!"); | |
} else if percentage_of_max >= 0.9 { | |
self.messenger | |
.send("Urgent warning: You've used up over 90% of your quota!"); | |
} else if percentage_of_max >= 0.75 { | |
self.messenger | |
.send("Warning: You've used up over 75% of your quota!"); | |
} | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use std::cell::RefCell; | |
struct MockMessenger { | |
sent_messages: RefCell<Vec<String>>, | |
} | |
impl MockMessenger { | |
fn new() -> MockMessenger { | |
MockMessenger { | |
sent_messages: RefCell::new(vec![]) | |
} | |
} | |
} | |
impl Messenger for MockMessenger { | |
fn send(&self, message: &str) { | |
self.sent_messages.borrow_mut().push(String::from(message)); | |
} | |
} | |
#[test] | |
fn it_sends_an_over_75_percent_warning_message() { | |
let mock_messenger = MockMessenger::new(); | |
let mut limit_tracker = LimitTracker::new_with_messenger(&mock_messenger, 100); | |
limit_tracker.set_value(80); | |
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); | |
} | |
} | |
fn main() { | |
let mut limit_tracker = LimitTracker::new(100); | |
limit_tracker.set_value(80); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// An attempt to have a struct with dependency which is a trait | |
// including a default constructor with provides the "production" dependency | |
// This approach uses generics which means types are compile time verified | |
// | |
// Adapted from https://doc.rust-lang.org/book/ch15-05-interior-mutability.html | |
pub trait Messenger { | |
fn send(&self, msg: &str); | |
} | |
pub struct ConcreteMessenger; | |
impl Messenger for ConcreteMessenger { | |
fn send(&self, msg: &str) { | |
println!("{}", msg); | |
} | |
} | |
pub struct LimitTracker<'a, T: Messenger> { | |
messenger: &'a T, | |
value: usize, | |
max: usize, | |
} | |
impl<'a, T> LimitTracker<'a, T> | |
where | |
T: Messenger, | |
{ | |
pub fn new(max: usize) -> LimitTracker<'a, ConcreteMessenger> { | |
LimitTracker { | |
messenger: &ConcreteMessenger, | |
value: 0, | |
max, | |
} | |
} | |
pub fn new_with_messenger(messenger: &'a T, max: usize) -> LimitTracker<'a, T> { | |
LimitTracker { | |
messenger, | |
value: 0, | |
max, | |
} | |
} | |
pub fn set_value(&mut self, value: usize) { | |
self.value = value; | |
let percentage_of_max = self.value as f64 / self.max as f64; | |
if percentage_of_max >= 1.0 { | |
self.messenger.send("Error: You are over your quota!"); | |
} else if percentage_of_max >= 0.9 { | |
self.messenger | |
.send("Urgent warning: You've used up over 90% of your quota!"); | |
} else if percentage_of_max >= 0.75 { | |
self.messenger | |
.send("Warning: You've used up over 75% of your quota!"); | |
} | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
use std::cell::RefCell; | |
struct MockMessenger { | |
sent_messages: RefCell<Vec<String>>, | |
} | |
impl MockMessenger { | |
fn new() -> MockMessenger { | |
MockMessenger { | |
sent_messages: RefCell::new(vec![]) | |
} | |
} | |
} | |
impl Messenger for MockMessenger { | |
fn send(&self, message: &str) { | |
self.sent_messages.borrow_mut().push(String::from(message)); | |
} | |
} | |
#[test] | |
fn it_sends_an_over_75_percent_warning_message() { | |
let mock_messenger = MockMessenger::new(); | |
let mut limit_tracker = LimitTracker::new_with_messenger(&mock_messenger, 100); | |
limit_tracker.set_value(80); | |
assert_eq!(mock_messenger.sent_messages.borrow().len(), 1); | |
} | |
} | |
fn main() { | |
// Note: This generics-based approach forces specifying the concrete class, | |
// which renders the constructor slightly less useful | |
let mut limit_tracker = LimitTracker::<ConcreteMessenger>::new(100); | |
limit_tracker.set_value(80); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment