Factory method pattern in Rust

Migo·2024년 6월 22일
0

Fluent Rust

목록 보기
24/27
post-thumbnail

Formal definition

Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

We say "decide" not because subclasses themselves decide what to create but because the decision actually comes down to which subclass is used to create the product.



Things to remind

  • Rust has no notion of class, albeit with the existence of trait object
  • Rust has no interface but trait


Problem

Say you want to franchise your Kimbab stores that has two menus: tuna and veggies. Doing business only in South Korea, it has been okay. But when it goes international, there comes a problem : localization. Turns out people in the U.S prefer more savory one as opposed to sweet one.

So, what Korean store and American store have in common is the following trait:

// Creator
trait TKimbabStore {

    // Whole responsibility of creating kimbab is delegated to the factory method
    fn make_kimbab(&self, kimbab_type: KimbabType) -> Box<dyn TKimbab>;

    // Template method to order kimbab
    fn order_kimbab(&self, kimbab_type: KimbabType) -> Box<dyn TKimbab> {
        let mut kimbab = self.make_kimbab(kimbab_type);
        kimbab.prepare();
        kimbab.cut();
        kimbab.boxed();
        kimbab
    }
}

// For type safety
enum KimbabType {
    Tuna,
    Veggie,
}

// Product
trait TKimbab {
    fn prepare(&mut self);
    fn cut(&mut self);
    fn boxed(&mut self);
}

Note that consumer of make_kimbab, which is order_kimbab is not aware of what kimbab they will get.
Therefore, we have TKimbabStore(Creator) that depends on abstract TKimbab(Product).



Implementation

struct NYKimbabStore;

impl TKimbabStore for NYKimbabStore {
    fn make_kimbab(&self, kimbab_type: KimbabType) -> Box<dyn TKimbab> {
        match kimbab_type {
            KimbabType::Tuna => Box::new(NYTunaKimbab),
            KimbabType::Veggie => Box::new(NYVeggieKimbab),
        }
    }
}

Notice that NYKimbabStore implements only make_kimbab and return NewYork style kimbab as per given order(here, KimbabType

Now, let's implement the Kimbabs:


struct NYTunaKimbab;
impl TKimbab for NYTunaKimbab {
    fn prepare(&mut self) {
        println!("Preparing NY Tuna Kimbab");
    }

    fn cut(&mut self) {
        println!("Cutting NY Tuna Kimbab");
    }
    fn boxed(&mut self) {
        println!("Boxing NY Tuna Kimbab");
    }
}

struct NYVeggieKimbab;
impl TKimbab for NYVeggieKimbab {
    fn prepare(&mut self) {
        println!("Preparing NY Veggie Kimbab");
    }
    fn cut(&mut self) {
        println!("Cutting NY Veggie Kimbab");
    }
    fn boxed(&mut self) {
        println!("Boxing NY Veggie Kimbab");
    }
}



Client

With the design pattern applied, we managed to draw the relationship between creator and product in a way that they follow dependency inversion principle(high-level component, TKimbabStore, depends on TKimbab and low level components also depends on TKimbab).

But when client makes an order, it's not to abstraction. In fact, instance creation is a reality of life! In the end, we have to create an instance so we could provide real value:

#[test]
fn test_ny_kimbab() {
    let ny_factory = NYKimbabStore;
    let _ny_veggie_imbab = ny_factory.order_kimbab(KimbabType::Veggie);
}
profile
Dude with existential crisis

0개의 댓글

관련 채용 정보