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()