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.
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).
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");
}
}
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);
}