I want to be able to create objects based on an enumeration class, and use a dictionary. Something like this:
class IngredientType(Enum):
SPAM = auto() # Some spam
BAKE_BEANS = auto() # Baked beans
EGG = auto() # Fried egg
class Ingredient(object):
pass
class Spam(Ingredient):
pass
class BakedBeans(Ingredient):
pass
class Egg(Ingredient):
pass
class IngredientFactory(object):
"""Factory makes ingredients"""
choice = {
IngredientType.SPAM: IngredientFactory.MakeSpam,
IngredientType.BAKED_BEANS: IngredientFactory.MakeBakedBeans,
IngredientType.EGG: IngredientFactory.MakeEgg
}
@staticmethod
def make(type):
method = choice[type]
return method()
@staticmethod
def makeSpam():
return Spam()
@staticmethod
def makeBakedBeans():
return BakedBeans()
@staticmethod
def makeEgg():
return Egg()
But I get the error:
NameError: name 'IngredientFactory' is not defined
For some reason the dictionary can't be created. Where am I going wrong here?
Python is not Java and doesn't require everything to be in a class. Here your IngredientFactory class has no states and only staticmethods, so it's actually just a singleton namespace, which in python is canonically done using the module as singleton namespace and plain functions. Also since Python classes are already callable, wrapping the instanciation in a function doesn't make sense. The simple, straightforwrad pythonic implementation would be:
# ingredients.py
class IngredientType(Enum):
SPAM = auto() # Some spam
BAKE_BEANS = auto() # Baked beans
EGG = auto() # Fried egg
class Ingredient(object):
pass
class Spam(Ingredient):
pass
class Beans(Ingredient):
pass
class Egg(Ingredient):
pass
_choice = {
IngredientType.SPAM: Spam,
IngredientType.BAKED_BEANS: Beans,
IngredientType.EGG: Egg
}
def make(ingredient_type):
cls = _choice[ingredient_type]
return cls()
And the client code:
import ingredients
egg = ingredients.make(ingredients.IngredientType.EGG)
# or much more simply:
egg = ingredients.Egg()
FWIW the IngredientType enum doesn't bring much here, and even makes things more complicated that they have to be - you could just use plain strings:
# ingredients.py
class Ingredient(object):
pass
class Spam(Ingredient):
pass
class Beans(Ingredient):
pass
class Egg(Ingredient):
pass
_choice = {
"spam": Spam,
"beans": Beans,
"egg": Egg
}
def make(ingredient_type):
cls = _choice[ingredient_type]
return cls()
And the client code:
import ingredients
egg = ingredients.make("egg")
Or if you really want to use an Enum, you can at least get rid of the choices dict by using the classes themselves as values for the enum as suggested by MadPhysicist:
# ingredients.py
class Ingredient(object):
pass
class Spam(Ingredient):
pass
class Beans(Ingredient):
pass
class Egg(Ingredient):
pass
class IngredientType(Enum):
SPAM = Spam
BEANS = Beans
EGG = Egg
@staticmethod
def make(ingredient_type):
return ingredient_type.value()
and the client code
from ingredients import IngredientType
egg = IngredientType.make(IngredientType.EGG)
But I really don't see any benefit here either
EDIT: you mention:
I am trying to implement the factory pattern, with the intent of hiding the creation of objects away. The user of the factory then just handles 'Ingredients' without knowledge of the concrete type
The user still have to specify what kind of ingredients he wants (the ingredient_type argument) so I'm not sure I understand the benefit here. What's your real use case actually ? (the problem with made up / dumbed down examples is that they don't tell the whole story).
After looking at Bruce Eckel's book I came up with this:
#Based on Bruce Eckel's book Python 3 example
# A simple static factory method.
from __future__ import generators
import random
from enum import Enum, auto
class ShapeType(Enum):
CIRCLE = auto() # Some circles
SQUARE = auto() # some squares
class Shape(object):
pass
class Circle(Shape):
def draw(self): print("Circle.draw")
def erase(self): print("Circle.erase")
class Square(Shape):
def draw(self): print("Square.draw")
def erase(self): print("Square.erase")
class ShapeFactory(object):
@staticmethod
def create(type):
#return eval(type + "()") # simple alternative
if type in ShapeFactory.choice:
return ShapeFactory.choice[type]()
assert 0, "Bad shape creation: " + type
choice = { ShapeType.CIRCLE: Circle,
ShapeType.SQUARE: Square
}
# Test factory
# Generate shape name strings:
def shapeNameGen(n):
types = list(ShapeType)
for i in range(n):
yield random.choice(types)
shapes = \
[ ShapeFactory.create(i) for i in shapeNameGen(7)]
for shape in shapes:
shape.draw()
shape.erase()
This gets the user to select a class type from the enumeration, and blocks any other type. It also means user's are less likely to write 'bad strings' with spelling mistakes. They just use the enums. The output from the test is then, something like this:
Circle.draw
Circle.erase
Circle.draw
Circle.erase
Square.draw
Square.erase
Square.draw
Square.erase
Circle.draw
Circle.erase
Circle.draw
Circle.erase
Square.draw
Square.erase
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With