exercises

Log | Files | Refs | README

command_pattern.rs (6353B)


      1 /! The Command Pattern transforms operations into objects, enabling us
      2 //! to store, pass, and, manipulate operations just like any other data
      3 
      4 use crate::{calculator::Calculator, expression::Expression};
      5 
      6 /// Define the Command pattern through a trait in Rust
      7 /// This trait establishes the contract that each command must follow:
      8 pub trait Command {
      9     /// Commands receive the calculator by mutable reference rather than
     10     /// owning it
     11     fn execute(&mut self, calculator: &mut Calculator) -> Result<Option<f64>, String>;
     12     fn undo(&self, calculator: &mut Calculator) -> Result<(), String>;
     13     fn description(&self) -> String;
     14 }
     15 
     16 /// Implement concrete commands
     17 pub struct EvaluateCommand {
     18     expression: String,
     19     expr_tree: Box<dyn Expression>,
     20     previous_result: Option<f64>,
     21 }
     22 
     23 
     24 impl EvaluateCommand {
     25     pub fn new(expression: String, expr_tree: Box<dyn Expression>) -> Self {
     26         Self {
     27             expression,
     28             expr_tree,
     29             previous_result: None,
     30         }
     31     }
     32 }
     33 
     34 
     35 // Implement a command to evaluate expressions
     36 impl Command for EvaluateCommand {
     37     fn execute(&mut self, calculator: &mut Calculator) -> Result<Option<f64>, String> {
     38         self.previous_result = calculator.last_result;
     39         // Evaluate expression using the composite pattern in last chapter:
     40         let result = self.expr_tree.evaluate(&calculator.variables)?;
     41         calculator.store_calculation(self.expression.clone(), result);
     42         Ok(Some(result))
     43     }
     44 
     45     fn undo(&self, calculator: &mut Calculator) -> Result<(), String> {
     46         calculator.last_result = self.previous_result;
     47         calculator.history.pop();
     48         Ok(())
     49     }
     50 
     51     fn description(&self) -> String {
     52         format!("Evaluate: {}", self.expression)
     53     }
     54 }
     55 
     56 pub struct SetVariableCommand {
     57     name: String,
     58     value: f64,
     59     previous_value: Option<f64>,
     60 }
     61 
     62 impl SetVariableCommand {
     63     pub fn new(name: String, value: f64) -> Self {
     64         Self {
     65             name,
     66             value,
     67             previous_value: None,
     68         }
     69     }
     70 }
     71 
     72 // Implement a command for setting variables
     73 impl Command for SetVariableCommand {
     74     fn execute(&mut self, calculator: &mut Calculator) -> Result<Option<f64>, String> {
     75         self.previous_value = calculator.get_variable(&self.name);
     76         calculator.set_variable(&self.name, self.value);
     77         Ok(None)
     78     }
     79 
     80     fn undo(&self, calculator: &mut Calculator) -> Result<(), String> {
     81         match self.previous_value {
     82             Some(value) => calculator.set_variable(&self.name, value)?,
     83             None => {
     84                 calculator.variables.remove(&self.name);
     85             }
     86         }
     87         Ok(())
     88     }
     89 
     90     fn description(&self) -> String {
     91         format!("Set {} = {}", self.name, self.value)
     92     }
     93 }
     94 
     95 /// CommandProcessor serves as the command management hub.
     96 /// Using `Box<dyn Command>` lets us store heterogeneous commands,
     97 /// including `EvaluateCommand` and `SetVariableCommand` in the same Vec.
     98 ///
     99 /// Rust's trait object system provides runtime polymorphism here:
    100 /// Each boxed command dispatches to its own `execute` and `undo`
    101 /// implementations through a vtable, while the processor treats
    102 /// them uniformly.
    103 pub struct CommandProcessor {
    104     calculator: Calculator,
    105     // These two stacks basically manages the commands and keep track of them:
    106     history: Vec<Box<dyn Command>>,
    107     undo_stack: Vec<Box<dyn Command>>,
    108 }
    109 
    110 impl CommandProcessor {
    111     /// This command: Box<dyn Command> is awfully similar to the DI pattern,
    112     /// like dependency injection - like the Dependency Inversion Principle
    113     /// in SOLID principles:
    114     ///
    115     /// "High-level modules should not depend on low-level modules.
    116     /// Both should depend on abstractions". means "Big parts of your program
    117     /// should not directly depend on small, detailed parts. Instead, both
    118     /// should depend on general ideas (interfaces)".
    119     ///
    120     /// So in this case the Command trait is the interface basically.
    121     pub fn execute(&mut self, mut command: Box<dyn Command>) -> Result<Option<f64>, String> {
    122         let result = command.execute(&mut self.calculator)?;
    123         self.history.push(command);
    124         self.undo_stack.clear();
    125         Ok(result)
    126     }
    127 
    128     // The undo and redo methods basically moves the command between two stacks
    129     // of management:
    130     pub fn undo(&mut self) -> Result<(), String> {
    131         if let Some(command) = self.history.pop() {
    132             command.undo(&mut self.calculator)?;
    133             self.undo_stack.push(command);
    134             Ok(())
    135         } else {
    136             Err("Nothing to undo".to_string())
    137         }
    138     }
    139 
    140     // The undo and redo methods basically moves the command between two stacks
    141     // of management:
    142     pub fn redo(&mut self) -> Result<(), String> {
    143         if let Some(mut command) = self.undo_stack.pop() {
    144             command.execute(&mut self.calculator)?;
    145             self.history.push(command);
    146             Ok(())
    147         } else {
    148             Err("Nothing to redo".to_string())
    149         }
    150     }
    151 }
    152 
    153 /// Calculator Facade Example
    154 struct CalculatorFacade {
    155     command_processor: CommandProcessor,
    156     parser: ExpressionParser,
    157 }
    158 
    159 /// This implementation showcases several Rust idioms: pattern matching for
    160 /// clean command dispatch, Result types for error propagation. The pattern
    161 /// extends naturally to composite commands.
    162 impl CalculatorFacade {
    163     pub fn process_input(&mut self, input: &str) -> Result<String, String> {
    164         match input.trim() {
    165             "undo" => {
    166                 self.command_processor.undo()?;
    167                 Ok("Operation undone".to_string())
    168             }
    169             "redo" => {
    170                 self.command_processor.redo()?;
    171                 Ok("Operation redone".to_string())
    172             }
    173             _ => {
    174                 let expr_tree = self.parser.parse(input)?;
    175                 let result = self
    176                     .command_processor
    177                     .execute(Box::new(EvaluateCommand::new(input.to_string(), expr_tree)))?;
    178                 if let Some(value) = result {
    179                     Ok(format!("{}", value))
    180                 } else {
    181                     Err("Failed to evaluate expression".to_string())
    182                 }
    183             }
    184         }
    185     }
    186 }
    187 
    188 struct ExpressionParser;
    189 
    190 impl ExpressionParser {
    191     pub fn parse(input: &str) {
    192         todo!()
    193     }
    194 }