Factory-Like Patterns: Python Examples

Notes on Factory Patterns.

Table of Contents

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

- Product classes.
- Factory class that has a method for creating and returning objects and a registry that contains references to product classes.
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

- Product classes.
- Factory class, which has method to create and return objects, e.g. 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()
    

Popular posts from this blog