Search code examples
design-patternsrustfactory-pattern

Rust fabric pattern, storing multiple factories in hashmap


I want to be able to store multiple factories in a single hashmap in order to add them to it later (by plugins for example) and then get each one by key-name in application (this is a resource manager).

The problem is in generic of Fabric trait, fabrics can create fruits of different types but I need to specify something in here HashMap<String, Box<dyn Fabric>>, for example HashMap<String, Box<Fabric<Apple>>> or HashMap<String, Box<Fabric<T>>> which is also not wery useful because as I said we can create really different fruits.

Also I guess there might be a problem in foo method, about borrowing content.

So how would you implement this "the rust way"?

use std::collections::HashMap;

trait Fruit {
    fn get_name(&self) -> String;
}

trait Fabric<T: Fruit> {
    fn build(&self) -> Box<T>;
}

struct Banana {}
impl Fruit for Banana {
    fn get_name(&self) -> String { String::from("I'm banana") }
}

struct BananaFabric {}
impl Fabric<Banana> for BananaFabric  {
    fn build(&self) -> Box<Banana> {
        Box::new(Banana {})
    }
}

struct Apple {}
impl Fruit for Apple {
    fn get_name(&self) -> String { String::from("I'm apple") }
}

struct AppleFabric {}
impl Fabric<Apple> for AppleFabric  {
    fn build(&self) -> Box<Apple> {
        Box::new(Apple {})
    }
}

struct C {
    map: HashMap<String, Box<dyn Fabric>>,
}

impl C {
    pub fn new() -> C {
        C {
            map: HashMap::new()
        }
    }

    pub fn foo(&self, key: String) {
        match self.map.get(&key) {
            Some(&fabric) => {
                let fruit = fabric.build();
                println!("{}", fruit.get_name())
            },
            _ => println!("No fabric found")
        }
    }
}

fn main() {
    let c = C::new();
    c.foo(String::from("bar"));
}

Solution

  • I can think of two options:

    Dynamic dispatch (trait objects):

    trait Fabric {
      fn build(&self) -> Box<dyn Fruit>;
    }
    
    [...]
    
    impl Fabric for BananaFabric  {
      fn build(&self) -> Box<dyn Fruit> {
        Box::new(Banana {})
      }
    }
    

    Using an enum:

    enum Fruits {
      Banana, 
      Apple
    }
    impl Fruit for Fruits {
      fn get_name(&self) -> String { 
        match self {
          Banana => String::from("I'm banana"),
          Apple => String::from("I'm apple"),
          _ => String::from("")
        }
      }
    }
    
    [...]
    
    impl Fabric for BananaFabric  {
      fn build(&self) -> Box<Fruits> {
        Box::new(Fruits::Banana)
      }
    }
    

    In both cases the foo method will look like:

    pub fn foo(&self, key: String) {
      match self.map.get(&key) {
        Some(fabric) => {
          let fruit = fabric.build();
            println!("{}", fruit.get_name())
          },
          _ => println!("No fabric found")
      }
    }