The factory pattern (or factory method pattern) is a creational design pattern which abstracts away the creation of objects via a factory class. This allows us to defer object creation and alter the type of objects created at runtime.

Example

from abc import ABC
from datetime import date
from dataclasses import dataclass


@dataclass
class BaseProduct(ABC):
    created_by: str
    product_name: str
    cost: int

    def __str__(self) -> str:
        return f'{self.created_by=}, {self.product_name=}, {self.cost=}'


class Product(BaseProduct):
    def __init__(self, created_by: str, product_name: str, cost: int) -> None:
        super().__init__(created_by, product_name, cost)

    def __str__(self) -> str:
        return f'Product: {super().__str__()}'


class PerishableProduct(BaseProduct):
    use_by_date: date

    def __init__(self, created_by: str, product_name: str, cost: int, use_by_date: date) -> None:
        super().__init__(created_by, product_name, cost)
        self.use_by_date = use_by_date

    def __str__(self) -> str:
        return f'PerishableProduct: {super().__str__()}, {self.use_by_date=}'


class ProductFactory():
    def __init__(self, username: str):
        self.created_by = username
    
    def create_product(self, product_name: str, cost: int, use_by_date: date=None) -> Product:
        if use_by_date:
            return PerishableProduct(self.created_by, product_name, cost, use_by_date)
        return Product(self.created_by, product_name, cost)

factory = ProductFactory('rob')
product = factory.create_product('item1', 100, None)
product2 = factory.create_product('item1', 100, date(1999, 12, 31))
print(product)
print(product2)

The core component in this example is ProductFactory and the create_product method. This takes details about the product we want to create via the method arguments and then determines what type of product we want to create based on them. As both products share a common interface, whatever calls this can code against the generic BaseProduct interface and not worry about the implementation details.

Why use it?

Let’s consider an example where you wish to take some legacy code and add a unit test to it. This code creates a new Product object and then calls purchase on it which goes on and contacts a third-party API to purchase the product. We wouldn’t want that code to be executed under test…

We could just wrap the offending code with something like if not is_test:, but this would only fix this instance and is not very helpful if you have multiple calls you wish to stud out. In this case, we could use the factory pattern to abstract that if condition away and then return a mocked object at test time.

This also aids with future extensibility as if we wish to implement a new version of the Product class with a differing implementation, it is now much simpler. All we would need to do is add the new product to the builder, and the calling code would not need to be altered as they are only concerned with the interface.