pythondesign-patternsumlaggregationfactory-pattern

Can this be considered a Factory Method pattern (or an equivalent)?


In a course I'm taking, a PizzaStore that uses a SimplePizzaFactory class which handles concrete pizza instantiation, described as follows: enter image description here

In the course, an intro to the factory method pattern is described by introducing the need to provide the PizzaStore with extra levels of specificity and the ability to provide the same types of Pizzas (Viggie, Cheese, etc..) but in a NY-Style and in a Chicago-Style, so we have a new set of subclasses (NYStyleViggiePizza, NYStyleCheesePizza, .. ChicagoStyleViggiePizza, ChicagoStyleCheesePizza, .. )

The solution introduced by the instructor was to use the factory method pattern as follows:

(UML)

enter image description here

Code re-written in python:

# Pizzas Subclasses are defined elsewhere
from abc import ABC, abstractmethod

class PizzaStore(ABC):
    @abstractmethod
    def create_pizza(self):
        pass

    def order_pizza(self,type_of_pizza):
        type_of_pizza = type_of_pizza.lower()
        pizza = self.create_pizza(type_of_pizza)
        pizza.prepare()
        pizza.bake()
        pizza.box()
        return pizza


class NYPizzaStore(PizzaStore):
    def create_pizza(self, type_of_pizza):
        if type_of_pizza == "cheese":
            pizza = NYStyleCheesePizza()
        
        elif type_of_pizza == "pepperoni":
            pizza = NYStylePepperoniPizza()

        elif type_of_pizza == "clam":
            pizza = NYStyleClamPizza()

        elif type_of_pizza == "viggie":
            pizza = NYStyleViggiePizza()
        else:
            raise Exception("You need to specify a type of NY pizza.")   
        return pizza


class ChicagoPizzaStore(PizzaStore):
    def create_pizza(self,type_of_pizza):
        if type_of_pizza == "cheese":
            pizza = ChicagoStyleCheesePizza()
        elif type_of_pizza == "pepperoni":
            pizza = ChicagoStylePepperoniPizza()

        elif type_of_pizza == "clam":
            pizza = ChicagoStyleClamPizza()

        elif type_of_pizza == "viggie":
            pizza = ChicagoStyleViggiePizza()
        else:
            raise Exception("You need to specify a type of NY pizza.")
        
        return pizza


# ===== Driver Code =====
# NY store
ny_pizza_store = NYPizzaStore()
ny_pizza_store.order_pizza("Cheese")
ny_pizza_store.order_pizza("Pepperoni")


print()


# Chicago store
chicago_pizza_store = ChicagoPizzaStore()
chicago_pizza_store.order_pizza("Cheese")
chicago_pizza_store.order_pizza("Pepperoni")

I tried the following design before jumping in to the factory method, where I kept the PizzaStore as it is and replaced the SimpleFactoryPizza with two new classes: NYPizzaFactory and ChicagoPizzaFactory

enter image description here

Code re-written in python:

class NYPizzaFactory():
    def create_pizza(self,type_of_pizza):
        if type_of_pizza == "cheese":
            pizza = NYStyleCheesePizza()
        elif type_of_pizza == "pepperoni":
            pizza = NYStylePepperoniPizza()

        elif type_of_pizza == "clam":
            pizza = NYStyleClamPizza()

        elif type_of_pizza == "viggie":
            pizza = NYStyleViggiePizza()
        else:
            raise Exception("You need to specify a type of NY pizza.")
        
        return pizza

class ChicagoPizzaFactory():
    def create_pizza(self,type_of_pizza):
        if type_of_pizza == "cheese":
            pizza = ChicagoStyleCheesePizza()
        elif type_of_pizza == "pepperoni":
            pizza = ChicagoStylePepperoniPizza()

        elif type_of_pizza == "clam":
            pizza = ChicagoStyleClamPizza()

        elif type_of_pizza == "viggie":
            pizza = ChicagoStyleViggiePizza()
        else:
            raise Exception("You need to specify a type of NY pizza.")
        
        return pizza

# PizzaStore is the same as before

class PizzaStore:
    def __init__(self, pizza_factory_obj):
        self.pizza_factory_obj = pizza_factory_obj

    def order_pizza(self,type_of_pizza):
        type_of_pizza = type_of_pizza.lower() 
        pizza = self.pizza_factory_obj.create_pizza(type_of_pizza)
        pizza.prepare()
        pizza.bake()
        pizza.box()
        return pizza

# ===== Driver Code ======
# NY Store
ny_pizza_factory = NYPizzaFactory()
ny_pizza_store = PizzaStore(ny_pizza_factory)
ny_pizza_store.order_pizza("Cheese")
print()
ny_pizza_store.order_pizza("Pepperoni")
print()

# Chicago Store
chicago_pizza_factory = ChicagoPizzaFactory()
chicago_pizza_store = PizzaStore(chicago_pizza_factory)
chicago_pizza_store.order_pizza("Cheese")
print()
chicago_pizza_store.order_pizza("Pepperoni")

I understand that a Factory Method lets a class defer instantiation to its subclasses, where these subclasses will include the implementation of that "factory method".

Question 1:

Question 2:

The factory method structure is generalized by the following UML: (from the course material)

enter image description here

In the "Design Patterns: Elements of Reusable Object-Oriented Software" book, the Factory method pattern's structure is described via this UML:

enter image description here


Solution

  • Q1: Is your implementation a factory?

    The factory method pattern intents to

    define an interface for creating an object, but let the subclass decide which class to instantiate - (GoF, page 107).

    Your design and re-implementation do exactly that and are factories.

    More detailed arguments

    In your re-written solution, according to your diagram, PizzaStore is some kind of context, which may use either a NYPizzaFactory or a ChicagoPizzaFactory or both. Your code is much clearer thant the UML, since you inject the factory into the store at construction.

    Your factories all appear to be concrete creators that produce instances of a product Pizza. Each concrete creator creates a different set of concrete pizzas. Taken individually, each of your XxxPizzaFactory appears to correspond to a concrete factory, the FactoryMethod() being called create_pizza().

    The only thing that is missing in the diagram and in the code, is a guarantee that the factories are interchangeable, by letting them inherit from a more general PizzaFactory. Fortunately for you, Python's dynamic typing can cope with the absence of same base class. But for maintenance purpose, better structure the classes in UML and in Python with the explicit subclassing.

    Factory or Abstract factory?

    The fact that each of your concrete factories can create different kind of Pizza is a variant of the pattern that is called "Parameterized factory method" GoF, page 110). So it is definitively the factory pattern, just that the create_pizza() takes an argument which specifies which concrete pizza to instantiate.

    This is not an abstract factory, because the abstract factory aims at creating families of related or dependent products. Each kind of product in the family has its specific factory method. This would be the case here, if you would have several create methods, such as create_pizza(), create_dessert(), create_wine(). This is not the case, here, since only one kind of product is created by each factory.

    Q2: Aggregation or dependency?

    First of all, GoF does not use UML (see GoF, page 363). UML was not yet officially published when the book was written:

    Interestingly, OMT, Booch and Objectory were the three leading OO notations that were merged to create UML.

    From an UML perspective,

    Additional information:

    PS: I used GoF to refer to "Design Patterns: Elements of Reusable Object-Oriented Software"