Consider the following python3 code:
from abc import ABC, abstractmethod
class Food(ABC):
_food_factory_map = {}
_recipes = {}
@classmethod
def getFood(cls, foodName):
return cls._food_factory_map[foodName]()
@classmethod
def registerFood(cls, foodName, recipe):
def callable(foodClass):
cls._food_factory_map[foodName] = foodClass
cls._recipes[foodName] = recipe
return foodClass
return callable
def __init__(self):
print("Starting Food prep")
@abstractmethod
def prepareFood(self):
...
def serveFood(self):
food = self.prepareFood()
print(food)
@Food.registerFood('Burger', 'Burger Recipe')
class Burger(Food):
def prepareFood(self):
return 'Burger is ready!'
@Food.registerFood('Pizza', 'Pizza Recipe')
class Pizza(Food):
def prepareFood(self):
return 'Pizza is ready!'
if __name__ == '__main__':
fs = ('Burger', 'Pizza')
for f in fs:
print(f"Food Recipe: {Food._recipes[f]}")
food = Food.getFood(f)
food.serveFood()
Here the abstract base class Food has class methods that implement the Factory design pattern. A client can register their own concrete classes with registerFood and retrieve them with getFood. The abstract base class also implements other abstract and concrete methods to define the interface.
One can also separate out the two class methods (getFood and registerFood) and the two class members (_food_factory_map and _recipes) into a class of it's own (FoodFactory)
class FoodFactory(ABC):
_food_factory_map = {}
_recipes = {}
@classmethod
def getFood(cls, foodName):
return cls._food_factory_map[foodName]()
@classmethod
def registerFood(cls, foodName, recipe):
def callable(foodClass):
cls._food_factory_map[foodName] = foodClass
cls._recipes[foodName] = recipe
return foodClass
return callable
All the client code will then change to using FoodFactory instead of Food.
@FoodFactory.registerFood('Pizza', 'Pizza Recipe')
class Pizza(Food):
def prepareFood(self):
return 'Pizza is ready!'
if __name__ == '__main__':
fs = ('Burger', 'Pizza')
for f in fs:
print(f"Food Recipe: {FoodFactory._recipes[f]}")
food = FoodFactory.getFood(f)
food.serveFood()
Which of these implementations is better and why? Or is it perfectly reasonable to use either? Is it language dependent?
One advantage I can think off is that with the second method, one can override the __new__ of FoodFactory to directly return an instance of the appropriate food rather than calling a separate getFood method. But that difference seems largely cosmetic.