Welcome, guest | Sign In | My Account | Store | Cart

An implementation of the Factory design pattern that doesn't require maintenance when a new specialized subclass is added.

Python, 43 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from types import TypeType

class Pizza(object):
    @staticmethod
    def containsIngredient(ingredient):
        return False
    def getPrice(self):
        return 0

class PizzaHamAndMushroom(Pizza):
    @staticmethod
    def containsIngredient(ingredient):
        return ingredient in ["ham", "mushroom"]
    def getPrice(self):
        return 8.50

class PizzaHawaiian(Pizza):
    @staticmethod
    def containsIngredient(ingredient):
        return ingredient in ["pineapple", "curry"]
    def getPrice(self):
        return 11.50

class PizzaFactory(object):
    @staticmethod
    def newPizza(ingredient):
        # Walk through all Pizza classes 
        pizzaClasses = [j for (i,j) in globals().iteritems() if isinstance(j, TypeType) and issubclass(j, Pizza)]
        for pizzaClass in pizzaClasses :
            if pizzaClass.containsIngredient(ingredient):
                return pizzaClass()
        #if research was unsuccessful, raise an error
        raise ValueError('No pizza containing "%s".' % ingredient)

def main():
    myPizza = PizzaFactory().newPizza("ham")
    print(myPizza.getPrice())
    myPizza2 = PizzaFactory().newPizza("curry")
    print(myPizza2.getPrice())
    myPizza3 = PizzaFactory().newPizza("beef")

if __name__ == "__main__":
    main()

Factory design pattern makes it easy to create specialized objects by instantiating a subclass chosen at runtime. It makes it easy to add progressively new specialized subclasses over time. However, most implementations of the Factory pattern require to be upgraded each time a new specialized subclass is created because it references explicitly all specialized subclasses. This implementation suppresses the need for maintaining the Factory by discovering specialized subclasses automatically. Choice of the subclass to instantiate relies on rules coded in the Factory. The rules are based on properties of the subclasses.

Le design pattern Factory permet de créer facilement des objets spécialisés en instanciant une sous-classe choisie lors de l'exécution. Il rend facile l'ajout de nouvelles sous-classes spécialisées dans le temps. Néanmoins, la plupart des implémentations de ce pattern nécessitent la mise à jour de la Factory à chaque ajout d'une nouvelle sous-classe car la Factory référence explicitement toues les sous-classes. Cette implémentation supprime le besoin de maintenir la Factory en découvrant automatiquement les sous-classes spécialisées. Le choix de la sous-classe à instancier se base sur des règles codées dans la Factory. Les règles se basent sur des propriétés des sous-classes.

10 comments

Konstantin Wirz 15 years ago  # | flag

'containsIngredient' should be a static method

Akira Fora (author) 15 years ago  # | flag

You're right. Recipe updated. Thanks.

David Lambert 15 years ago  # | flag

I'm baffled. Why would you charge me 11.50 for curry without pineapple?

self isn't used in newPizza method of PizzaFactory class. Just write newPizza as a function and call it.

Where you have written

if ingredient in ingredientList:
    return True
else:
    return False

instead just write

return ingredient in ingredientList

There's a terrible amount of duplicated code. Let's change the containsIngredient method to __contains__. Now we can exploit the python syntax and write "if ingredient in pizza:" where you had had "if pizza.contains(ingredient):". Next, discard the static methods, install some class dependent informations, and rewrite the base Pizza class. The following python3 program produces the same output, but I still don't get it. Merci.

TypeType = type(type)

class Pizza:

    ingredientList = []
    price = 0.00

    def __contains__(self,ingredient):
        return ingredient in self.ingredientList

    def getPrice(self):
        return self.price

class PizzaHamAndMushroom(Pizza):
    ingredientList = ["ham", "mushroom"]
    price = 8.50

class PizzaHawaiian(Pizza):
    ingredientList = ["pineapple", "curry"]
    price = 11.50

def newPizza(ingredient):
    # Walk through all Pizza classes 
    pizzaClasses = [j for (i,j) in globals().items() if isinstance(j, TypeType) and issubclass(j, Pizza)]
    for pizzaClass in pizzaClasses :
        pizza = pizzaClass()
        if ingredient in pizza:
            return pizza
    #if research was unsuccessful, raise an error
    raise ValueError('No pizza containing "%s".' % ingredient)

def main():
    myPizza = newPizza("ham")
    print(myPizza.getPrice())
    myPizza2 = newPizza("curry")
    print(myPizza2.getPrice())
    myPizza3 = newPizza("beef")

if __name__ == "__main__":
    main()
Akira Fora (author) 15 years ago  # | flag

I updated the implementation of containsIngredient(). As for your other points : The rule used by the factory to choose the subclass depends entirely on your needs. In this recipe, the rule is ‘make a pizza that contains the desired ingredient’. Using __contains__ seems right in this very example, but real use cases of the factory pattern are rarely as simple as this recipe. For instance, you may need a factory that asks to all the subclasses how they perform in a given situation and selects the best performer. Same objection for the rewrite of the base class : real specialized subclasses usually do more than provide a mere constant.

David Lambert 15 years ago  # | flag

Thank you. and, Ah! The essence of the recipe is

scan global dictionary for best fit class. return it or raise an error.

(your code returns the object; my constructors often need arguments.)

I suppose I'll vote +1 for the recipe. I'd never have thought of it, probably because I write deterministic code. That is, if I need a new class variant at run time I build it at run time and therefor know its features. Your code is a good reminder that one can often "go kilometers" by tinkering with the various dictionaries python builds to hold name spaces.

Chris Bartos 12 years, 6 months ago  # | flag

I can see how this design will introduce problems in the future? For example, you keep adding pizzas (ie, Pepperoni Pizza, Meat Lovers Pizza) you're ingredient list for each would be:

["pepperoni"]

and

["ham", "pepperoni", "chicken"]

So looping through the ingredient list with "ham" might get you either a Meat Lover's Pizza or Ham and Mushroom. If you loop through with "pepperoni", you might get either Pepperoni Pizza or Meat Lovers Pizza. I don't see this as a very robust design. It's bound to create problems in the future.

Chris Bartos 12 years, 6 months ago  # | flag

Please excuse my horrible grammar and spelling errors.

Akira Fora (author) 12 years, 3 months ago  # | flag

Hi Chris. Please read my earlier comment. Again, this recipe is not about whether it is clever to pick the first pizza that contains a given ingredient. It is about adding new pizzas without updating the rest of your code. A good real-life use case would be adding codecs to an image viewer. Pizzas would be codecs, ingredients would be filename extensions, getPrice() method would be render() method.

varun 11 years, 3 months ago  # | flag

including a try except in main method makes sure the uninitiated does not get a traceback when you copy and run the recipe

def main():
try:
    myPizza = PizzaFactory().newPizza("ham")
    print(myPizza.getPrice())
    myPizza2 = PizzaFactory().newPizza("curry")
    print(myPizza2.getPrice())
    myPizza3 = PizzaFactory().newPizza("beef")
except ValueError as ve:
    print ve
Martin Miller 9 years, 2 months ago  # | flag

Rather than rummaging through all the entire globals() namespace looking for Pizza subclasses you could find all its direct subclasses with simply:

    for pizzaClass in Pizza.__subclasses__():
        . . .

However to get all descendants a recursive function like the following would be needed (adapted from this answer & my comments to the question How can I find all subclasses of a given class in Python? on stackoverflow):

def all_subclasses(classname):
    subclasses = eval(classname).__subclasses__()
    return subclasses + [g for s in subclasses
                            for g in all_subclasses(s.__name__)]

and used instead like so:

    for pizzaClass in all_subclasses('Pizza'):
        . . .
Created by Akira Fora on Wed, 11 Mar 2009 (MIT)
Python recipes (4591)
Akira Fora's recipes (2)

Required Modules

Other Information and Tasks