Factory-Like Patterns: Python Examples
See also Abstract Factory Pattern: Description and Examples, Factory Method Pattern: Description and Examples.
IRL, developers don't strictly follow the pattern descriptions. There can be one Factory class that can work with any product. This Factory centralizes the creation process in one class. Useful if you don't need to create separate factories to create products.
Factory-Like Patterns
Registration-Based Factory
Key Components
import abc class AbstractProduct(metaclass=abc.ABCMeta): @abc.abstractmethod def interface(self): pass class ConcreteProduct1(AbstractProduct): def interface(self): print("ConcreteProduct1") class ConcreteProduct2(AbstractProduct): def interface(self): print("ConcreteProduct2") class ProductFactory: _registry = {} @classmethod def register(cls, key, product_class): cls._registry[key] = product_class @classmethod def create(cls, key): # factory method product_class = cls._registry.get(key) if not product_class: raise ValueError(f"No Product registered under key: {key}") return product_class() # register products ProductFactory.register("cp1", ConcreteProduct1) ProductFactory.register("cp2", ConcreteProduct2) def main(): product = ProductFactory.create("cp1") product.interface() if __name__ == "__main__": main()
Simple Factory Pattern
Key Components
create(type)
.import abc class AbstractProduct(metaclass=abc.ABCMeta): @abc.abstractmethod def interface(self): pass class ConcreteProduct1(AbstractProduct): def interface(self): print("ConcreteProduct1") class ConcreteProduct2(AbstractProduct): def interface(self): print("ConcreteProduct2") class ProductFactory: @staticmethod def create(class_type): if class_type == 'cp1': return ConcreteProduct1() efif class_type == 'cp2': return ConcreteProduct2() else: raise ValueError('Wrong class type passed.') def main(): product = ProductFactory.create("cp1") product.interface() if __name__ == "__main__": main()
Multiple Constructors in Python
Key Components
Product class with methods that can take various arguments to create an object. These methods provide an alternative way to create objects.
Products
All objects are instances of the same class, not different classes. Sometimes we need to customize the creation of one type of product. There is no need to create factories for each case.
Factory Methods
The from_...
methods are additional constructors. This is common for Python classes as an alternative to constructor overloading (also functools @singledispatchmethod). Such named constructors can serve as factory methods.
class Product: def __init__(self, x: list): self.x = x @classmethod def from_string(cls, s): # return a product object, like a factory method return cls([int(i) for i in s]) product = Product([1, 2, 3]) print(product.x) product1 = Product.from_string("123") print(product1.x)
Python-specific Way of Creating Objects
Using __new__
__new__ is a static method intended mainly to allow subclasses to customize instance creation.
The return value of __new__
is usually an instance of that class. However, it is possible to create and return objects of other classes in __new__
.
import abc class AbstractProduct(metaclass=abc.ABCMeta): def __init__(self, attr): self.attr = attr @abc.abstractmethod def interface(self): pass class ConcreteProduct1(AbstractProduct): def __init__(self, attr, other): # AbstractProduct.__init__ super().__init__(attr) self.other = other def interface(self): print("ConcreteProduct1", self.attr, self.other) class ConcreteProduct2(AbstractProduct): def interface(self): print("ConcreteProduct2", self.attr) class ProductFactory: # object is superclass def __new__(cls, class_name, *args, **kwargs): # same as object.__new__(class_name) obj = super().__new__(class_name) # call to set self attributes obj.__init__(*args, **kwargs) return obj def main(): product = ProductFactory(ConcreteProduct1, "attr", "other") product.interface() product1 = ProductFactory(ConcreteProduct2, "attr") product1.interface() if __name__ == "__main__": main()