1

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:

  • By definition, Is my solution not considered a factory pattern? What are the differences and downsides to the approach I tried compared to the factory method represented?

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

  • The arrow between the factory and the product represents "aggregation" from the course material, while the UML diagram in the book represents "Dependency" (I think), Which one represents the correct relationship and why?
Ahmed Alhallag
  • 311
  • 2
  • 11
  • Shouldn't your method be static? – Klaus D. Sep 08 '20 at 23:39
  • If you mean the second implementation, to make create_pizza() static in the factory classes, then I think you're correct, honestly I was just following along with the course's java implementation and comparing multiple approaches from different sources to see the differences. – Ahmed Alhallag Sep 08 '20 at 23:48
  • Perhaps you could limit your question to one question rather than the current four. – khelwood Sep 08 '20 at 23:52
  • @khelwood I thought they were targeting the same point, but I'll do that. – Ahmed Alhallag Sep 08 '20 at 23:53
  • 1
    At first factories are much less common in Python than in Java for example. Then there is `__new__` which can actually return an object of a different class on object creation. And then it the factory has no state it is not needed to create a factory instance before using the method. It can be static. – Klaus D. Sep 08 '20 at 23:56
  • @KlausD.can you explain what do you mean by 'not having a state' if you don't mind? – Ahmed Alhallag Sep 09 '20 at 00:08
  • What are those connectors in the lower picture? They are definitely not UML. – qwerty_so Sep 09 '20 at 09:07

2 Answers2

4

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:

  • GoF uses OMT and not UML for class diagrams
  • GoF uses a mix of Booch and Objectory for interaction diagrams.

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:

  • Why a simple association would be more suitable than the aggregation in GoF: it's the second part of an answer to an unrelated question (in its comments, I listed the incont uses in GoF which alternates between the two notations)
  • This answer and this answer to unrelated questions explain why the fact of creating, returning or temporarily using a class in a method is not sufficient for making an association (and since aggregation is stronger than a simple association, it's even less sufficient for an aggregation).

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

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    as usual a nice detailed answer – bruno Sep 10 '20 at 07:03
  • You should point out that the OP's last picture is no UML. – qwerty_so Sep 10 '20 at 08:14
  • @qwerty_so I did: the last picture is a diagram of the GoF book as explained by OP, and the first thing I answer for Q2 is that GoF does not use UML but OMT (which by the way contributed to popularize this discussable practice of representing object composition with an aggregate in the diagram, aggregate that OMT’s inventor, Rumbaugh himself, called a [modelling placebo](https://martinfowler.com/bliki/AggregationAndComposition.html)) ;-) – Christophe Sep 10 '20 at 09:59
  • 1
    Ah, I see. I was not aware of that dialect. But placebo is a nice one :-) – qwerty_so Sep 10 '20 at 10:23
  • @qwerty_so Yes, the quote really nails it! OMT is almost archeology. It was published in 1991 (so 1 year before Jacobson’s OOSE and Booch in 1992). GoF was published in 1995 (based on work of Gamma’s PhD thesis in 1991). At that time Booch and Rumbaugh already worked on unifying their notations and they were just joined in 1995 by Jacobson. UML was adopted by OMG in 1997. GoF could have updated the book with an UML edition (like Rumbaugh did with his own book) But I think that emergence of Java and the debate of interface implementation vs. inheritance complicated discussions about the models – Christophe Sep 10 '20 at 11:25
2

The pattern you have described latterly is the Abstract Factory Pattern; there are several factory implementations that inherit from the same abstract factory. This is certainly a variant on the factory pattern in answer to question #1.

For question #2, the aggregation vs dependency thing is really a matter of style. The GoF's use of Dependency is weaker (logically) than an Aggregation (i.e. Factory depends on Product is a weaker concept than Factory aggregates Product). Both get the message across though -- i.e. either works.

Personally I prefer the Dependency because I don't think that the Factory actually does aggregate the Product. For aggregation -- think of a Car aggregating Wheels. This is not really a parallel concept to the relationship between Factory and Product. Once the Product is created, the Factory has nothing more to do with it. Continuing the example, once a Car Factory has made a Car, the Car leaves the Factory and never returns -- so it's difficult to argue that the Car is somehow an aggregate part of the Factory that made it. However, that's my opinion.

I believe the aggregations in course material diagrams are the wrong way around. The Client would aggregate the (Abstract) Factory, not the other way around, and similarly the Factory would aggregate the Product. I'm also not entirely sure why the Client would not directly reference the Product, since the whole point in a Factory is to abstract object creation, not usage.

muszeo
  • 2,312
  • 9
  • 13
  • Interesting arguments. It is nevertheless still a factory pattern, since concrete factories create only one kind of products. More precisely, it's a "Parameterized factory method" as explained in GoF, page 110, a variant in which the factory is allowed to instantiate different kind of conrete products, based on a parameter of the factory method. The abstract factory implies a family of interrelated or interdependent products, i.e. several kind of abstrct products, and an abstract factory method for each. – Christophe Sep 09 '20 at 20:19