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 }