exercises

Log | Files | Refs | README

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 }