Factory Method Pattern

Issue

  • At a certain point in the program, you need an object of a certain type, but you don’t know which sub-class you actually need

  • Decisions which type of object is created depends on business logic

  • Multiple business logic sets might exist in parallel (e.g. create totally randomly, create by making sure each object type is equally often instantiated)

  • The instantiation logic might be needed at different places in the code -> duplication

  • If the logic changes, these changes must be done to any usage in the code -> hard to maintain

To summarize, we should use the Factory Method Pattern where there is logic involved in the creation of an object and when this logic repeats itself

Design Principles & Definition

Definition

The Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Solution

  • Object instantiation is moved into separate classes

  • These classes do nothing but instantiate the proper object and return it

  • These classes are called Factory classes

  • Various amount of Factories classes for the same object may exist, each implementing a different type of business logic on which object is instantiated and returned

  • Multiple factories should implement the same Factory interface, which thereby defines a blueprint for these Factory classes

        classDiagram
    Product <|-- ConcreteProduct : implement
    ConcreteProduct <|-- ConcreteCreator : creates
    Creator <|-- ConcreteCreator : implement
    class Creator {
        +factory_method()
    }
    class ConcreteCreator {
        +factory_method()
    }
    
  • The ConcreateCreator creates objects of type Product, in which the internal logic of its factory_method() decides which ConcreteProduct is eventually returned

  • The Creator also assigns Product as the return type of factory_method()

Example

Find a template example for Python at https://refactoring.guru/design-patterns/factory-method/python/example.

from abc import ABC
from random import randrange


class Animal(ABC):

    def say_hello(self):
        pass


class Dog(Animal):

    def say_hello(self):
        print("Wuff")


class Cat(Animal):

    def say_hello(self):
        print("Miau")


class Duck(Animal):

    def say_hello(self):
        print("Quack")


class AnimalFactory(ABC):

    def create(self):
        pass


class BalancedFactory(AnimalFactory):

    def create(self):
        # Implementation:
        # create animals randomly, but make sure the same amount is instantiated
        # after three calls and re-balance
        pass


class RandomFactory(AnimalFactory):

    def create(self):
        random_number = randrange(1, 3)
        if random_number == 1:
            return Dog()
        if random_number == 2:
            return Cat()
        if random_number == 3:
            return Duck()


if __name__ == "__main__":
    factory = RandomFactory()
    animal = factory.create()
    animal.say_hello()

Hint

There might be scenarios where the Adaptee is external and out of our control and not instantiatable (e.g. only using static methods). In that case, Adaptee cannot be passed into the constructor of Adapter, but needs to be directly called in the Adapter’s methods (e.g. requests()):

class Adapter:

    def __init__(self):
        pass

    def request():
        Adaptee.specific_request()