Factory Method

Factory Method

The factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. Defines an interface for creating an instance of an object but lets the class which implements the interface decide which class to instantiate.

Example use cases of Factory Method

use case: ordering a pizza from a pizza store

Let's say we have a pizza shop and we want to order the pizza. When we order the pizza, the pizza shop will need to do the following functionalities. 1. prepare it 2. bake it 3. cut it 4. box it

In this scenario, we can define classes PizzaStore, Pizza. The class PizzaStore will acts as a client which will talk to the class Pizza which contains the methods prepare, bake, cut and box.

Let's dive into the code.

# service
class Pizza:
    def prepare(self):
        """ prepare the pizza """
    def bake(self):
        """ bake the pizza """
    def cut(self):
        """ cut the baked pizza into a required shape """
    def box(self):
        """ move the pizza into the box """

# client
class PizzaStore:
    def order_pizza(self):
        """ order and get pizza """
        pizza = Pizza()
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza

if __name__ == '__main__':
    store = PizzaStore()
    store.order_pizza()

Above code will work for only one type of pizza. If we have multiple pizza type classes then we need to have a Factory to decide the class to be used for the client request. If we don't use the Factory then client needs to handle the logic with the pizza type classes which is not recommended. Let's the see the code without the Factory pattern.

from abc import ABC, abstractmethod

class Pizza(ABC):
    @abstractmethod
    def prepare(self):
        """ prepare the pizza """
    @abstractmethod
    def bake(self):
        """ bake the pizza """
    @abstractmethod
    def cut(self):
        """ cut the baked pizza into a required shape """
    @abstractmethod
    def box(self):
        """ move the pizza into the box """

class PizzaMixin:
    pizza_type = ""
    def prepare(self):
        """ prepare the pizza """
        print(f"prepare {self.pizza_type} pizza")
    def bake(self):
        """ bake the pizza """
        print(f"bake {self.pizza_type} pizza")
    def cut(self):
        """ cut the baked pizza into a required shape """
        print(f"cut {self.pizza_type} pizza")
    def box(self):
        """ move the pizza into the box """
        print(f"box {self.pizza_type} pizza")

class CheesePizza(PizzaMixin, Pizza):
    pizza_type = "cheese"

class VegPizza(PizzaMixin, Pizza):
    pizza_type = "veg"

# client
class PizzaStore:
    def order_pizza(self, pizza_type):
        """ order and get pizza """
        # logic to decide the pizza class
        pizza = None
        if(pizza_type == "cheese"):
            pizza = CheesePizza()
        elif(pizza_type == "veg"):
            pizza = VegPizza()

        if pizza is None:
            raise Exception("Invalid pizza type")
        # we can have more types of pizza classes
        # end
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza

if __name__ == '__main__':
    store = PizzaStore()
    store.order_pizza("cheese")

In the above code we can see that the client class PizzaStore need to know the details of the classes CheesePizza, VegPizza. If we want to add a new pizza type then we need to modify the client class code. By using the factory pattern we can avoid it. Let's implement the Factory pattern by seperating the pizza type logic

    pizza = None
    if(pizza_type == "cheese"):
        pizza = CheesePizza()
    elif(pizza_type == "veg"):
        pizza = VegPizza()

Let's create a factory class and move logic.

class PizzaFactory:
    def create_pizza(self, pizza_type):
        pizza = None
        if(pizza_type == "cheese"):
            pizza = CheesePizza()
        elif(pizza_type == "veg"):
            pizza = VegPizza()
        if pizza is None:
            raise Exception("Invalid pizza type")
        return pizza

Now, we need to rework on the class PizzaStore so that it will use the factory.

class PizzaStore:
    def order_pizza(self, pizza_type):
        """ order and get pizza """
        factory = PizzaFactory()
        pizza = factory.create_pizza(pizza_type)
        pizza.prepare()
        pizza.bake()
        pizza.cut()
        pizza.box()
        return pizza

If you see the class PizzaStore it's clean and if we add a new pizza type also it will work without changing the code.

You can find the complete code at github https://github.com/AnjaneyuluBatta505/learnbatta/blob/master/python/design_patterns/creational_design_patterns/factory_method.py