Factory Method Pattern: Description and Examples

Notes on Factory Method Pattern.

Table of Contents

See also Abstract Factory Pattern: Description and Examples, Factory-Like Patterns: Python Examples.


Description


Purpose

Define an interface for creating a single object, but let subclasses decide which class to instantiate.


Key Components

Creator <— Concrete Creators, Product <— Concrete Products.

There is one type of Product. Creator declares one factory method (function). Concrete Creators (similar to factories) implement one factory method that return Concrete Product.

A product can have multiple variants. Each variant in this case is a specific implementation of the same product type. Concrete Creator produces one variant, but it is only one Concrete Product.


Inheritance and Object Composition

Pattern uses inheritance to define classes and interfaces. Subclasses override factory methods or product methods declared in superclasses (subtype polymorphism).

Pattern primarily relies on inheritance. The focus is on deferring the creation of a concrete product (instantiation) to subclasses.


Bussines Logic

Concrete Creators typically do not contain any methods not related to object creation. Business logic is mainly contained in product classes and client code (app class or def main).

Creator and Concrete Creators are responsible for creating objects. Creator can contain some business logic applicable to all product variants. Creator may also define a default implementation of the factory method that returns a default Concrete Product object.

Product class can define methods that include some common business logic applicable to all Concrete Products. Concrete Product classes often contain methods that perform operations related to the type of object they represent.

Client code uses an instance of a Concrete Creator and does not necessarily manipulate the product object directly. Product classes provide a unified interface for using the products. Concrete Creator inherits methods that use this product interface from the Creator class.

The decision about which creator to instantiate is made in the client code based on some conditions or predefined parameters.


Object Customization

You can use the __init__ method in an abstract class either to initialize some common attributes or to enforce all subclasses to pass certain arguments. Concrete classes (products, factories, creators) should call super().__init__() if they extend or override __init__. Accordingly, factory methods can also take arguments.

Factory Method can be used with other patterns: Abstract Factory, Strategy, and Template Method.


Pattern Example in Python


import abc


class Creator(metaclass=abc.ABCMeta):
    """Declare one factory method (one Product type)."""

    def __init__(self):
        self.product = self._factory_method()

    @abc.abstractmethod
    def _factory_method(self):
        pass

    def some_operation(self):
        """Some core business logic."""
        self.product.interface()

    #def some_operation(self):
        #"""Some core business logic."""
        #product = self._factory_method()
        #result = product.interface()
        #return result


class ConcreteCreator1(Creator):
    """Implement factory method to create concrete product object.
    "is-a" relationships (inheritance):
        ConcreteProduct1 -> Product
    "has-a" (specifically, "creates-a") relationships (association):
        Creator creates an object.
        The creator has a factory method that returns the product.
    """

    def _factory_method(self):
        # override the factory method to return product variant 1
        return ConcreteProduct1()


class ConcreteCreator2(Creator):
    """Implement factory method to create concrete product object.
    "is-a" relationships (inheritance):
        ConcreteProduct2 -> Product
    "has-a" (specifically, "creates-a") relationships (association):
        Creator creates an object.
        The creator has a factory method that returns the product.
    """

    def _factory_method(self):
        # override the factory method to return product variant 2
        return ConcreteProduct2()


class Product(metaclass=abc.ABCMeta):
    """Declare a type of product.
    interface is a common method for concrete products of this type.
    """

    @abc.abstractmethod
    def interface(self):
        pass


class ConcreteProduct1(Product):
    """Define product variant 1. Implement the Product interface."""

    def interface(self):
        print("ConcreteProduct1")


class ConcreteProduct2(Product):
    """Define product variant 2. Implement the Product interface."""

    def interface(self):
        print("ConcreteProduct2")


def main():
    """
    There could be an if/elif/else block 
    to choose which product variant to create.
    """
    
    # product variant 1
    concrete_creator1 = ConcreteCreator1()
    concrete_creator1.product.interface()
    concrete_creator1.some_operation()
    # product variant 2
    concrete_creator2 = ConcreteCreator2()
    concrete_creator2.product.interface()
    concrete_creator2.some_operation()


if __name__ == "__main__":
    main()