In a course I'm taking, a PizzaStore
that uses a SimplePizzaFactory
class which handles concrete pizza instantiation, described as follows:
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)
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
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)
In the "Design Patterns: Elements of Reusable Object-Oriented Software" book, the Factory method pattern's structure is described via this UML:
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.
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,
the relation between ConcreteCreator
and ConcreteProduct
is a «create»
dependency. In fact, there should also be a «create»
dependency between Creator
and Product
.
There should be no aggregation nor no association between the Factory
and the Product
(unless, either product would keep track of the factory that created it or the factory would keep a list of all the products it created).
There is a problem about the side of the agregation: you could use an aggregation between Client
and Factory
, but with the diamond on the client side. Nevertheless, while it is not fundamentally wrong a simple association would better represent the relation between the two classes.
Additional information:
PS: I used GoF to refer to "Design Patterns: Elements of Reusable Object-Oriented Software"