Abstract Factory Pattern: Description and Examples

Notes on Abstract Factory Pattern.

Table of Contents

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


Description


Purpose

Provide an interface for creating families of related or dependent objects.


Key Components

Abstract Factory <— Concrete Factories, Abstract Product <— Concrete Products.

There is more than one type of Abstract Product. Abstract Factory declares a factory method (function) for each product type. Concrete Factories are implementing factory methods that return Concrete Products.

Abstract Products are a basic family of products. Each product from this basic family can have several variants. These variants are implemented as Concrete Products.

Each variant in this case is a group of specific implementations of products of a base family. These variant groups are also considered families.

Concrete Factory produces one variant, a coherent set of Concrete Products of different types, but with some common features.

A family is a group of products intended to be used together in a specific context.


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 object composition. Concrete Factory can create a family of products that are not related in inheritance hierarchy.


Bussines Logic

Factories 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).

Abstract Factory and Concrete Factories are responsible for creating objects.

Abstract Product classes 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 Factory to create products. Product classes provide a unified interface for using the products.

The decision about which factory 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) should call super().__init__() if they extend or override __init__. Accordingly, factory methods can also take arguments.

Abstract Factory can be used with other patterns: Factory Method, Singleton, Prototype, Dependency Injection.


Pattern Example in Python


import abc


class AbstractFactory(metaclass=abc.ABCMeta):
    """Abstract Factory declares a factory method per product type.  
    create_product_a and create_product_b are 
    common methods(interface) for concrete factories.
    """

    @abc.abstractmethod
    def create_product_a(self):
        pass

    @abc.abstractmethod
    def create_product_b(self):
        pass


class ConcreteFactory1(AbstractFactory):
    """Implements factory methods to create concrete product objects.
    "is-a" relationships (inheritance):
        ConcreteProductA1 -> AbstractProductA
        ConcreteProductB1 -> AbstractProductB
    "has-a" (specifically, "creates-a") relationships (association):
        Factory creates objects.
        The factory has factory methods that return products.
    "part-of" (composition/aggregation): 
        Variant 1 is a family of logically related objects of different types.
    """

    def create_product_a(self):
    	# override the factory method to return product type A, variant 1
        return ConcreteProductA1()

    def create_product_b(self):
        # override the factory method to return product type B, variant 1
        return ConcreteProductB1()


class ConcreteFactory2(AbstractFactory):
    """Implement factory methods to create concrete product objects.
    "is-a" relationships (inheritance):
        ConcreteProductA2 -> AbstractProductA
        ConcreteProductB2 -> AbstractProductB
    "has-a" (specifically, "creates-a") relationships (association):
        Factory creates objects.
        The factory has factory methods that return products.
    "part-of" (composition/aggregation):
        Variant 2 is a family of logically related objects of different types.
    """

    def create_product_a(self):
        # override the factory method to return product type A, variant 2
        return ConcreteProductA2()

    def create_product_b(self):
        # override the factory method to return product type B, variant 2
        return ConcreteProductB2()


class AbstractProductA(metaclass=abc.ABCMeta):
    """Declare a type of product.
    interface_a is a common method for concrete products of A type.
    """

    @abc.abstractmethod
    def interface_a(self):
        pass


class ConcreteProductA1(AbstractProductA):
    """Define product type A, variant 1.
    Implement the AbstractProductA interface.
    """

    def interface_a(self):
        print("ConcreteProductA1")


class ConcreteProductA2(AbstractProductA):
    """Define product type A, variant 2.
    Implement the AbstractProductA interface.
    """

    def interface_a(self):
        print("ConcreteProductA2")


class AbstractProductB(metaclass=abc.ABCMeta):
    """Declare a type of product.
    interface_b is a common method for concrete products of B type.
    """

    @abc.abstractmethod
    def interface_b(self):
        pass


class ConcreteProductB1(AbstractProductB):
    """Define product type B, variant 1.
    Implement the AbstractProductB interface.
    """

    def interface_b(self):
        print("ConcreteProductB1")


class ConcreteProductB2(AbstractProductB):
    """Define product type B, variant 2.
    Implement the AbstractProductB interface.
    """

    def interface_b(self):
        print("ConcreteProductB2")


def main():
    """
    There could be an if/elif/else block 
    to choose which product family to create.
    """
    
    # products of types A and B, variant 1
    concrete_factory1 = ConcreteFactory1()
    product_a = concrete_factory1.create_product_a()
    product_a.interface_a()
    product_b = concrete_factory1.create_product_b()
    product_b.interface_b()
    # products of types A and B, variant 2
    concrete_factory2 = ConcreteFactory2()
    product_a = concrete_factory2.create_product_a()
    product_a.interface_a()
    product_b = concrete_factory2.create_product_b()
    product_b.interface_b()


if __name__ == "__main__":
    main()