An implementation of the Factory design pattern that doesn't require maintenance when a new specialized subclass is added.
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.
'containsIngredient' should be a static method
You're right. Recipe updated. Thanks.
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
instead just write
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.
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.
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.
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.
Please excuse my horrible grammar and spelling errors.
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.
including a try except in main method makes sure the uninitiated does not get a traceback when you copy and run the recipe
Rather than rummaging through all the entire
globals()
namespace looking forPizza
subclasses you could find all its direct subclasses with simply: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):
and used instead like so: