Skip to content

Creational Patterns

Factory Method Pattern

Definition

The 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.

Design Principle

  • Dependency Inversion Principle: high-level components should not depend on our low-level components; rather, they should both depend on abstractions. Depend upon abstractions. Do not depend on concrete classes.

Best practices

  • By placing all creation code in one object or method, we avoid duplication in the code and provide one place for maintenance.
  • Clients depend only upon interfaces reather than the concrete classes required to instantiate objects => Allow to program to an interface, not an implementation => make the code more flexible and extensible in the future.

Original Diagram

Example codes

from abc import ABC, abstractmethod

# Abstract Product
class Pizza(ABC):
    def __init__(self):
        self.name = ""
        self.dough = ""
        self.sauce = ""
        self.toppings = []

    def prepare(self):
        print(f"Preparing {self.name}")
        print(f"Tossing {self.dough}")
        print(f"Adding {self.sauce}")
        print("Adding toppings:")
        for topping in self.toppings:
            print(f"  {topping}")

    @staticmethod
    def bake():
        print("Bake for 25 minutes at 350")

    @staticmethod
    def cut():
        print("Cutting the pizza into diagonal slices")

    @staticmethod
    def box():
        print("Place pizza in official PizzaStore box")

# Concrete Products
class NYStyleCheesePizza(Pizza):
    def __init__(self):
        super().__init__()
        self.name = "NY Style Sauce and Cheese Pizza"
        self.dough = "Thin Crust Dough"
        self.sauce = "Marinara Sauce"
        self.toppings.append("Grated Reggiano Cheese")

class ChicagoStyleCheesePizza(Pizza):
    def __init__(self):
        super().__init__()
        self.name = "Chicago Style Deep Dish Cheese Pizza"
        self.dough = "Extra Thick Crust Dough"
        self.sauce = "Plum Tomato Sauce"
        self.toppings.append("Shredded Mozzarella Cheese")

    @staticmethod
    def cut():
        print("Cutting the pizza into square slices")

# Abstract Creator
class PizzaStore(ABC):
    @abstractmethod
    def create_pizza(self, item):
        pass

    def order_pizza(self, pizza_type):
        pizza = self.create_pizza(pizza_type)
        print(f"--- Making a {pizza.name} ---")
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza

# Concrete Creators
class NYPizzaStore(PizzaStore):
    def create_pizza(self, item):
        if item == "cheese":
            return NYStyleCheesePizza()
        # Add other pizza types here
        else:
            return None

class ChicagoPizzaStore(PizzaStore):
    def create_pizza(self, item):
        if item == "cheese":
            return ChicagoStyleCheesePizza()
        # Add other pizza types here
        else:
            return None

# Client code
if __name__ == "__main__":
    ny_store = NYPizzaStore()
    chicago_store = ChicagoPizzaStore()

    ny_pizza = ny_store.order_pizza("cheese")
    print(f"Ethan ordered a {ny_pizza.name}\n")

    chicago_pizza = chicago_store.order_pizza("cheese")
    print(f"Joel ordered a {chicago_pizza.name}\n")

"""Output:
--- Making a NY Style Sauce and Cheese Pizza ---
Preparing NY Style Sauce and Cheese Pizza
Tossing Thin Crust Dough
Adding Marinara Sauce
Adding toppings:
  Grated Reggiano Cheese
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Sauce and Cheese Pizza

--- Making a Chicago Style Deep Dish Cheese Pizza ---
Preparing Chicago Style Deep Dish Cheese Pizza
Tossing Extra Thick Crust Dough
Adding Plum Tomato Sauce
Adding toppings:
  Shredded Mozzarella Cheese
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Deep Dish Cheese Pizza
"""
package main

import "fmt"

// Pizza interface
type Pizza interface {
    Prepare()
    Bake()
    Cut()
    Box()
    GetName() string
}

// BasePizza struct
type BasePizza struct {
    name     string
    dough    string
    sauce    string
    toppings []string
}

func (b *BasePizza) Prepare() {
    fmt.Printf("Preparing %s\n", b.name)
    fmt.Printf("Tossing %s\n", b.dough)
    fmt.Printf("Adding %s\n", b.sauce)
    fmt.Println("Adding toppings:")
    for _, topping := range b.toppings {
        fmt.Printf("  %s\n", topping)
    }
}

func (b *BasePizza) Bake() {
    fmt.Println("Bake for 25 minutes at 350")
}

func (b *BasePizza) Cut() {
    fmt.Println("Cutting the pizza into diagonal slices")
}

func (b *BasePizza) Box() {
    fmt.Println("Place pizza in official PizzaStore box")
}

func (b *BasePizza) GetName() string {
    return b.name
}

// NYStyleCheesePizza struct
type NYStyleCheesePizza struct {
    BasePizza
}

func NewNYStyleCheesePizza() *NYStyleCheesePizza {
    pizza := &NYStyleCheesePizza{}
    pizza.name = "NY Style Sauce and Cheese Pizza"
    pizza.dough = "Thin Crust Dough"
    pizza.sauce = "Marinara Sauce"
    pizza.toppings = []string{"Grated Reggiano Cheese"}
    return pizza
}

// ChicagoStyleCheesePizza struct
type ChicagoStyleCheesePizza struct {
    BasePizza
}

func NewChicagoStyleCheesePizza() *ChicagoStyleCheesePizza {
    pizza := &ChicagoStyleCheesePizza{}
    pizza.name = "Chicago Style Deep Dish Cheese Pizza"
    pizza.dough = "Extra Thick Crust Dough"
    pizza.sauce = "Plum Tomato Sauce"
    pizza.toppings = []string{"Shredded Mozzarella Cheese"}
    return pizza
}

func (c *ChicagoStyleCheesePizza) Cut() {
    fmt.Println("Cutting the pizza into square slices")
}

// PizzaStore interface
type PizzaStore interface {
    CreatePizza(pizzaType string) Pizza
    OrderPizza(pizzaType string) Pizza
}

// BasePizzaStore struct
type BasePizzaStore struct {
    createPizza func(pizzaType string) Pizza
}

func (b *BasePizzaStore) CreatePizza(pizzaType string) Pizza {
    return b.createPizza(pizzaType)
}

func (b *BasePizzaStore) OrderPizza(pizzaType string) Pizza {
    pizza := b.CreatePizza(pizzaType)
    if pizza == nil {
        fmt.Printf("Sorry, we don't have %s pizza.\n", pizzaType)
        return nil
    }
    fmt.Printf("--- Making a %s ---\n", pizza.GetName())
    pizza.Prepare()
    pizza.Bake()
    pizza.Cut()
    pizza.Box()
    return pizza
}

// NYPizzaStore struct
type NYPizzaStore struct {
    BasePizzaStore
}

func NewNYPizzaStore() *NYPizzaStore {
    store := &NYPizzaStore{}
    store.createPizza = func(pizzaType string) Pizza {
        if pizzaType == "cheese" {
            return NewNYStyleCheesePizza()
        }
        // Add other pizza types here
        return nil
    }
    return store
}

// ChicagoPizzaStore struct
type ChicagoPizzaStore struct {
    BasePizzaStore
}

func NewChicagoPizzaStore() *ChicagoPizzaStore {
    store := &ChicagoPizzaStore{}
    store.createPizza = func(pizzaType string) Pizza {
        if pizzaType == "cheese" {
            return NewChicagoStyleCheesePizza()
        }
        // Add other pizza types here
        return nil
    }
    return store
}

func main() {
    nyStore := NewNYPizzaStore()
    chicagoStore := NewChicagoPizzaStore()

    pizza := nyStore.OrderPizza("cheese")
    if pizza != nil {
        fmt.Printf("Ethan ordered a %s\n\n", pizza.GetName())
    }

    pizza = chicagoStore.OrderPizza("cheese")
    if pizza != nil {
        fmt.Printf("Joel ordered a %s\n\n", pizza.GetName())
    }

    // Test with a non-existent pizza type
    pizza = nyStore.OrderPizza("pepperoni")
    if pizza != nil {
        fmt.Printf("Ethan ordered a %s\n\n", pizza.GetName())
    }
}

/* OUTPUT:
--- Making a NY Style Sauce and Cheese Pizza ---
Preparing NY Style Sauce and Cheese Pizza
Tossing Thin Crust Dough
Adding Marinara Sauce
Adding toppings:
  Grated Reggiano Cheese
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Sauce and Cheese Pizza

--- Making a Chicago Style Deep Dish Cheese Pizza ---
Preparing Chicago Style Deep Dish Cheese Pizza
Tossing Extra Thick Crust Dough
Adding Plum Tomato Sauce
Adding toppings:
  Shredded Mozzarella Cheese
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Deep Dish Cheese Pizza

Sorry, we don't have pepperoni pizza.
*/