utilities.rs (4611B)
1 //! Decorator pattern 2 3 use std::{cell::RefCell, collections::HashMap, time::Instant}; 4 5 pub trait Expression { 6 fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String>; 7 fn to_string(&self) -> String; 8 fn precedence(&self) -> u8 { 9 0 10 } 11 } 12 13 // Leaf node for number values 14 #[derive(Debug, Clone)] 15 pub struct NumberExpression { 16 pub value: f64, 17 } 18 19 impl NumberExpression { 20 pub fn new(value: f64) -> Self { 21 Self { value } 22 } 23 } 24 25 impl Expression for NumberExpression { 26 fn evaluate(&self, _variables: &HashMap<String, f64>) -> Result<f64, String> { 27 Ok(self.value) 28 } 29 30 fn to_string(&self) -> String { 31 format!("{}", self.value) 32 } 33 } 34 35 /// Composites Pattern 36 pub struct VariableExpression { 37 pub name: String, 38 } 39 40 impl Expression for VariableExpression { 41 fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> { 42 variables 43 .get(&self.name) 44 .copied() 45 .ok_or_else(|| format!("Undefined variable: {}", self.name)) 46 } 47 48 fn to_string(&self) -> String { 49 self.name.clone() 50 } 51 } 52 53 /// Logging decorator 54 /// 55 /// Notice that Logger is itself a trait object, which means we can swap in 56 /// different logging backends (console, file, network) without changing 57 /// the decorator 58 pub trait Logger { 59 fn log(&self, message: &str); 60 } 61 62 pub struct ConsoleLogger; 63 64 impl Logger for ConsoleLogger { 65 fn log(&self, message: &str) { 66 println!("[LOG] {}", message); 67 } 68 } 69 70 /// This holds both the expression it decorates, and the logger it uses, 71 /// composing two independent abstractions 72 pub struct LoggingExpression { 73 inner: Box<dyn Expression>, 74 logger: Box<dyn Logger>, 75 } 76 77 impl LoggingExpression { 78 pub fn new(inner: Box<dyn Expression>, logger: Box<dyn Logger>) -> Self { 79 Self { inner, logger } 80 } 81 } 82 83 impl Expression for LoggingExpression { 84 fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> { 85 self.logger 86 .log(&format!("Evaluating: {}", self.inner.to_string())); 87 let result = self.inner.evaluate(variables); 88 match &result { 89 Ok(val) => self.logger.log(&format!("Result: {}", val)), 90 Err(err) => self.logger.log(&format!("Error: {}", err)), 91 } 92 result 93 } 94 95 /// These delegation are critial, it makes the decorator transparent to any 96 /// code that doesn't care about logging 97 fn to_string(&self) -> String { 98 self.inner.to_string() 99 } 100 fn precedence(&self) -> u8 { 101 self.inner.precedence() 102 } 103 } 104 105 /// The timing decorator pattern 106 pub struct TimingExpression { 107 inner: Box<dyn Expression>, 108 } 109 110 impl TimingExpression { 111 pub fn new(inner: Box<dyn Expression>) -> Self { 112 Self { inner } 113 } 114 } 115 116 /// The decorator captures the current time before delegating to the inner expression 117 impl Expression for TimingExpression { 118 fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> { 119 let start = Instant::now(); 120 let result = self.inner.evaluate(variables); 121 let duration = start.elapsed(); 122 println!("Evaluation took: {:?}", duration); 123 result 124 } 125 126 /// These delegations are critial, it makes the decorator transparent to any 127 /// code that doesn't care about timing 128 fn to_string(&self) -> String { 129 self.inner.to_string() 130 } 131 fn precedence(&self) -> u8 { 132 self.inner.precedence() 133 } 134 } 135 136 /// The caching decorator pattern with interior mutability 137 pub struct CachingExpression { 138 inner: Box<dyn Expression>, 139 // The RefCell<Option<f64>> allows us to mutate the cached value through 140 // a shared reference 141 last_result: RefCell<Option<f64>>, 142 } 143 144 impl CachingExpression { 145 pub fn new(inner: Box<dyn Expression>) -> Self { 146 Self { 147 inner, 148 last_result: RefCell::new(None), 149 } 150 } 151 152 pub fn invalidate_cache(&self) { 153 *self.last_result.borrow_mut() = None; 154 } 155 } 156 157 impl Expression for CachingExpression { 158 fn evaluate(&self, variables: &HashMap<String, f64>) -> Result<f64, String> { 159 // Check if the result already cached 160 if let Some(result) = *self.last_result.borrow() { 161 return Ok(result); 162 } 163 // Cache the result 164 let result = self.inner.evaluate(variables)?; 165 *self.last_result.borrow_mut() = Some(result); 166 Ok(result) 167 } 168 169 fn to_string(&self) -> String { 170 self.inner.to_string() 171 } 172 173 fn precedence(&self) -> u8 { 174 self.inner.precedence() 175 } 176 }