exercises

Log | Files | Refs | README

commit 05e9362363c44ef39c34b36046f2b3c4de08d72c
parent 4ea903382eda05f3da1b8d83d54eb2a9dcff3c78
Author: ling0x <ling0x@users.noreply.github.com>
Date:   Tue, 23 Jun 2026 22:42:03 +0100

refactor: command pattern

Diffstat:
Mdesign_patterns_in_rust/behavioral_patterns/src/calculator.rs | 14++++++++++++++
Mdesign_patterns_in_rust/behavioral_patterns/src/command_pattern.rs | 151+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 159 insertions(+), 6 deletions(-)

diff --git a/design_patterns_in_rust/behavioral_patterns/src/calculator.rs b/design_patterns_in_rust/behavioral_patterns/src/calculator.rs @@ -7,3 +7,17 @@ pub struct Calculator { } pub struct Calculation; + +impl Calculator { + pub fn store_calculation(&self, expression: String, result: f64) { + todo!() + } + + pub fn get_variable(&self, name: &str) -> Option<f64> { + todo!() + } + + pub fn set_variable(&self, name: &str, value: f64) -> Result<(), String> { + todo!() + } +} diff --git a/design_patterns_in_rust/behavioral_patterns/src/command_pattern.rs b/design_patterns_in_rust/behavioral_patterns/src/command_pattern.rs @@ -1,4 +1,4 @@ -//! The Command Pattern transforms operations into objects, enabling us +/! The Command Pattern transforms operations into objects, enabling us //! to store, pass, and, manipulate operations just like any other data use crate::{calculator::Calculator, expression::Expression}; @@ -20,17 +20,36 @@ pub struct EvaluateCommand { previous_result: Option<f64>, } + +impl EvaluateCommand { + pub fn new(expression: String, expr_tree: Box<dyn Expression>) -> Self { + Self { + expression, + expr_tree, + previous_result: None, + } + } +} + + +// Implement a command to evaluate expressions impl Command for EvaluateCommand { fn execute(&mut self, calculator: &mut Calculator) -> Result<Option<f64>, String> { - todo!() + self.previous_result = calculator.last_result; + // Evaluate expression using the composite pattern in last chapter: + let result = self.expr_tree.evaluate(&calculator.variables)?; + calculator.store_calculation(self.expression.clone(), result); + Ok(Some(result)) } fn undo(&self, calculator: &mut Calculator) -> Result<(), String> { - todo!() + calculator.last_result = self.previous_result; + calculator.history.pop(); + Ok(()) } fn description(&self) -> String { - todo!() + format!("Evaluate: {}", self.expression) } } @@ -40,16 +59,136 @@ pub struct SetVariableCommand { previous_value: Option<f64>, } +impl SetVariableCommand { + pub fn new(name: String, value: f64) -> Self { + Self { + name, + value, + previous_value: None, + } + } +} + +// Implement a command for setting variables impl Command for SetVariableCommand { fn execute(&mut self, calculator: &mut Calculator) -> Result<Option<f64>, String> { - todo!() + self.previous_value = calculator.get_variable(&self.name); + calculator.set_variable(&self.name, self.value); + Ok(None) } fn undo(&self, calculator: &mut Calculator) -> Result<(), String> { - todo!() + match self.previous_value { + Some(value) => calculator.set_variable(&self.name, value)?, + None => { + calculator.variables.remove(&self.name); + } + } + Ok(()) } fn description(&self) -> String { + format!("Set {} = {}", self.name, self.value) + } +} + +/// CommandProcessor serves as the command management hub. +/// Using `Box<dyn Command>` lets us store heterogeneous commands, +/// including `EvaluateCommand` and `SetVariableCommand` in the same Vec. +/// +/// Rust's trait object system provides runtime polymorphism here: +/// Each boxed command dispatches to its own `execute` and `undo` +/// implementations through a vtable, while the processor treats +/// them uniformly. +pub struct CommandProcessor { + calculator: Calculator, + // These two stacks basically manages the commands and keep track of them: + history: Vec<Box<dyn Command>>, + undo_stack: Vec<Box<dyn Command>>, +} + +impl CommandProcessor { + /// This command: Box<dyn Command> is awfully similar to the DI pattern, + /// like dependency injection - like the Dependency Inversion Principle + /// in SOLID principles: + /// + /// "High-level modules should not depend on low-level modules. + /// Both should depend on abstractions". means "Big parts of your program + /// should not directly depend on small, detailed parts. Instead, both + /// should depend on general ideas (interfaces)". + /// + /// So in this case the Command trait is the interface basically. + pub fn execute(&mut self, mut command: Box<dyn Command>) -> Result<Option<f64>, String> { + let result = command.execute(&mut self.calculator)?; + self.history.push(command); + self.undo_stack.clear(); + Ok(result) + } + + // The undo and redo methods basically moves the command between two stacks + // of management: + pub fn undo(&mut self) -> Result<(), String> { + if let Some(command) = self.history.pop() { + command.undo(&mut self.calculator)?; + self.undo_stack.push(command); + Ok(()) + } else { + Err("Nothing to undo".to_string()) + } + } + + // The undo and redo methods basically moves the command between two stacks + // of management: + pub fn redo(&mut self) -> Result<(), String> { + if let Some(mut command) = self.undo_stack.pop() { + command.execute(&mut self.calculator)?; + self.history.push(command); + Ok(()) + } else { + Err("Nothing to redo".to_string()) + } + } +} + +/// Calculator Facade Example +struct CalculatorFacade { + command_processor: CommandProcessor, + parser: ExpressionParser, +} + +/// This implementation showcases several Rust idioms: pattern matching for +/// clean command dispatch, Result types for error propagation. The pattern +/// extends naturally to composite commands. +impl CalculatorFacade { + pub fn process_input(&mut self, input: &str) -> Result<String, String> { + match input.trim() { + "undo" => { + self.command_processor.undo()?; + Ok("Operation undone".to_string()) + } + "redo" => { + self.command_processor.redo()?; + Ok("Operation redone".to_string()) + } + _ => { + let expr_tree = self.parser.parse(input)?; + let result = self + .command_processor + .execute(Box::new(EvaluateCommand::new(input.to_string(), expr_tree)))?; + if let Some(value) = result { + Ok(format!("{}", value)) + } else { + Err("Failed to evaluate expression".to_string()) + } + } + } + } +} + +struct ExpressionParser; + +impl ExpressionParser { + pub fn parse(input: &str) { todo!() } }