Skip to main content
added 137 characters in body
Source Link
Finomnis
  • 23.5k
  • 2
  • 33
  • 38

Of course there exists a large mount of other variations of the same patterns. Which one you implement is up to you and your usecase.

Of course there exists a large mount of other variations of the same patterns. Which one you implement is up to you and your usecase.

added 1456 characters in body
Source Link
Finomnis
  • 23.5k
  • 2
  • 33
  • 38

Of course, if you don't need to create a new object every time and the act of executing doesn't consume it, you can reduce the complexity a little by bypassing the factory pattern:

use std::collections::HashMap; pub trait Executable { fn run(&self); } pub struct NodeA {} impl Executable for NodeA { fn run(&self) { println!("NodeA::run()"); } } pub struct NodeB {} impl Executable for NodeB { fn run(&self) { println!("NodeB::run()"); } } struct ExecutableRegistry { executables: HashMap<&'static str, Box<dyn Executable>>, } impl ExecutableRegistry { fn new() -> Self { Self { executables: HashMap::new(), } } fn register_executable( &mut self, command: &'static str, executable: impl Executable + 'static, ) { self.executables.insert(command, Box::new(executable)); } fn try_execute(&self, s: &str) { if let Some(node) = self.executables.get(s) { node.run(); } else { println!("'{}' not found.", s); } } } fn main() { let mut registry = ExecutableRegistry::new(); registry.register_executable("NodeA", NodeA {}); registry.register_executable("NodeB", NodeB {}); registry.try_execute("NodeA"); registry.try_execute("NodeB"); registry.try_execute("NodeC"); } 

Of course, if you don't need to create a new object every time and the act of executing doesn't consume it, you can reduce the complexity a little by bypassing the factory pattern:

use std::collections::HashMap; pub trait Executable { fn run(&self); } pub struct NodeA {} impl Executable for NodeA { fn run(&self) { println!("NodeA::run()"); } } pub struct NodeB {} impl Executable for NodeB { fn run(&self) { println!("NodeB::run()"); } } struct ExecutableRegistry { executables: HashMap<&'static str, Box<dyn Executable>>, } impl ExecutableRegistry { fn new() -> Self { Self { executables: HashMap::new(), } } fn register_executable( &mut self, command: &'static str, executable: impl Executable + 'static, ) { self.executables.insert(command, Box::new(executable)); } fn try_execute(&self, s: &str) { if let Some(node) = self.executables.get(s) { node.run(); } else { println!("'{}' not found.", s); } } } fn main() { let mut registry = ExecutableRegistry::new(); registry.register_executable("NodeA", NodeA {}); registry.register_executable("NodeB", NodeB {}); registry.try_execute("NodeA"); registry.try_execute("NodeB"); registry.try_execute("NodeC"); } 
Source Link
Finomnis
  • 23.5k
  • 2
  • 33
  • 38

For smaller, static number of nodes, I think a match - case construct is perfectly fine.

But if you have a larger number of nodes, or the available nodes is dynamically changing, I would implement something like this:

pub trait Executable { fn run(&self); } pub struct NodeA {} impl Executable for NodeA { fn run(&self) { println!("NodeA::run()"); } } pub struct NodeB {} impl Executable for NodeB { fn run(&self) { println!("NodeB::run()"); } } pub trait Matcher { fn try_match(&self, s: &str) -> Option<Box<dyn Executable>>; } pub struct NodeAMatcher; pub struct NodeBMatcher; impl Matcher for NodeAMatcher { fn try_match(&self, s: &str) -> Option<Box<dyn Executable>> { (s == "NodeA").then(|| Box::new(NodeA {}) as Box<dyn Executable>) } } impl Matcher for NodeBMatcher { fn try_match(&self, s: &str) -> Option<Box<dyn Executable>> { (s == "NodeB").then(|| Box::new(NodeB {}) as Box<dyn Executable>) } } struct MatcherRegistry { matchers: Vec<Box<dyn Matcher>>, } impl MatcherRegistry { fn new() -> Self { Self { matchers: vec![] } } fn register_matcher(&mut self, matcher: impl Matcher + 'static) { self.matchers.push(Box::new(matcher)); } fn try_get_node(&self, s: &str) -> Option<Box<dyn Executable>> { self.matchers .iter() .filter_map(|matcher| matcher.try_match(s)) .next() } fn try_execute(&self, s: &str) { if let Some(node) = self.try_get_node(s) { node.run(); } else { println!("'{}' not found.", s); } } } fn main() { let mut registry = MatcherRegistry::new(); registry.register_matcher(NodeAMatcher); registry.register_matcher(NodeBMatcher); registry.try_execute("NodeA"); registry.try_execute("NodeB"); registry.try_execute("NodeC"); } 
NodeA::run() NodeB::run() 'NodeC' not found. 

Here, you have a factory pattern.

The structs NodeAMatcher and NodeBMatcher are factories for NodeA and NodeB. They can check if the input matches, and then create an Executable object.

Then, you collect all possible factories (or Matchers here) in a registry, here called MatcherRegistry. You can then, at runtime, add or remove matchers as you wish.