commit 05e9362363c44ef39c34b36046f2b3c4de08d72c
parent 4ea903382eda05f3da1b8d83d54eb2a9dcff3c78
Author: ling0x <ling0x@users.noreply.github.com>
Date: Tue, 23 Jun 2026 22:42:03 +0100
refactor: command pattern
Diffstat:
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!()
}
}