Creational patterns provide object creation mechanisms that increase flexibility and reuse of existing code.
Taken fully from refactoring.guru, head there for a more detailed explanation. No code was written by me, I am just able to execute it here.
Factory Method
The factory method provides interface in a superclass, but allows subclasses to deal with flavors. It is usually responsible for creating a single product.
from __future__ import annotationsfrom abc import ABC, abstractmethodclass Creator(ABC):""" The Creator class declares the factory method that is supposed to return an object of a Product class. The Creator's subclasses usually provide the implementation of this method. """@abstractmethoddef factory_method(self):""" Note that the Creator may also provide some default implementation of the factory method. """passdef some_operation(self) ->str:""" Also note that, despite its name, the Creator's primary responsibility is not creating products. Usually, it contains some core business logic that relies on Product objects, returned by the factory method. Subclasses can indirectly change that business logic by overriding the factory method and returning a different type of product from it. """# Call the factory method to create a Product object. product =self.factory_method() # KEY POINT: product is created dynamically# Now, use the product. result =f"Creator: The same creator's code has just worked with {product.operation()}"return result"""Concrete Creators override the factory method in order to change the resultingproduct's type."""class ConcreteCreator1(Creator):""" Note that the signature of the method still uses the abstract product type, even though the concrete product is actually returned from the method. This way the Creator can stay independent of concrete product classes. """def factory_method(self) -> Product:return ConcreteProduct1() class ConcreteCreator2(Creator):def factory_method(self) -> Product:return ConcreteProduct2()class Product(ABC):""" The Product interface declares the operations that all concrete products must implement. """@abstractmethoddef operation(self) ->str:pass"""Concrete Products provide various implementations of the Product interface."""class ConcreteProduct1(Product):def operation(self) ->str:return"{Result of the ConcreteProduct1}"class ConcreteProduct2(Product):def operation(self) ->str:return"{Result of the ConcreteProduct2}"def client_code(creator: Creator) ->None:""" The client code works with an instance of a concrete creator, albeit through its base interface. As long as the client keeps working with the creator via the base interface, you can pass it any creator's subclass. """print(f"Client: I'm not aware of the creator's class, but it still works.\n"f"{creator.some_operation()}", end="")if__name__=="__main__":print("App: Launched with the ConcreteCreator1.") client_code(ConcreteCreator1())print("\n")print("App: Launched with the ConcreteCreator2.") client_code(ConcreteCreator2())
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
Abstract Factory
Abstract factory pattern is expansion of Factory method, but it creates entire families of related products.
For example there are products (A, B, …) and flavor factories (1, 2, …). Client code only deals with abstract factory.
from __future__ import annotationsfrom abc import ABC, abstractmethodclass AbstractFactory(ABC):""" The Abstract Factory interface declares a set of methods that return different abstract products. These products are called a family and are related by a high-level theme or concept. Products of one family are usually able to collaborate among themselves. A family of products may have several variants, but the products of one variant are incompatible with products of another. """@abstractmethoddef create_product_a(self) -> AbstractProductA:pass@abstractmethoddef create_product_b(self) -> AbstractProductB:passclass ConcreteFactory1(AbstractFactory):""" Concrete Factories produce a family of products that belong to a single variant. The factory guarantees that resulting products are compatible. Note that signatures of the Concrete Factory's methods return an abstract product, while inside the method a concrete product is instantiated. """def create_product_a(self) -> AbstractProductA:return ConcreteProductA1()def create_product_b(self) -> AbstractProductB:return ConcreteProductB1()class ConcreteFactory2(AbstractFactory):""" Each Concrete Factory has a corresponding product variant. """def create_product_a(self) -> AbstractProductA:return ConcreteProductA2()def create_product_b(self) -> AbstractProductB:return ConcreteProductB2()class AbstractProductA(ABC):""" Each distinct product of a product family should have a base interface. All variants of the product must implement this interface. """@abstractmethoddef useful_function_a(self) ->str:pass"""Concrete Products are created by corresponding Concrete Factories."""class ConcreteProductA1(AbstractProductA):def useful_function_a(self) ->str:return"The result of the product A1."class ConcreteProductA2(AbstractProductA):def useful_function_a(self) ->str:return"The result of the product A2."class AbstractProductB(ABC):""" Here's the the base interface of another product. All products can interact with each other, but proper interaction is possible only between products of the same concrete variant. """@abstractmethoddef useful_function_b(self) ->None:""" Product B is able to do its own thing... """pass@abstractmethoddef another_useful_function_b(self, collaborator: AbstractProductA) ->None:""" ...but it also can collaborate with the ProductA. The Abstract Factory makes sure that all products it creates are of the same variant and thus, compatible. """pass"""Concrete Products are created by corresponding Concrete Factories."""class ConcreteProductB1(AbstractProductB):def useful_function_b(self) ->str:return"The result of the product B1."""" The variant, Product B1, is only able to work correctly with the variant, Product A1. Nevertheless, it accepts any instance of AbstractProductA as an argument. """def another_useful_function_b(self, collaborator: AbstractProductA) ->str: result = collaborator.useful_function_a()returnf"The result of the B1 collaborating with the ({result})"class ConcreteProductB2(AbstractProductB):def useful_function_b(self) ->str:return"The result of the product B2."def another_useful_function_b(self, collaborator: AbstractProductA):""" The variant, Product B2, is only able to work correctly with the variant, Product A2. Nevertheless, it accepts any instance of AbstractProductA as an argument. """ result = collaborator.useful_function_a()returnf"The result of the B2 collaborating with the ({result})"def client_code(factory: AbstractFactory) ->None:""" The client code works with factories and products only through abstract types: AbstractFactory and AbstractProduct. This lets you pass any factory or product subclass to the client code without breaking it. """ product_a = factory.create_product_a() product_b = factory.create_product_b()print(f"{product_b.useful_function_b()}")print(f"{product_b.another_useful_function_b(product_a)}", end="")if__name__=="__main__":""" The client code can work with any concrete factory class. """print("Client: Testing client code with the first factory type:") client_code(ConcreteFactory1()) # KEY POINTprint("\n")print("Client: Testing the same client code with the second factory type:") client_code(ConcreteFactory2())
Client: Testing client code with the first factory type:
The result of the product B1.
The result of the B1 collaborating with the (The result of the product A1.)
Client: Testing the same client code with the second factory type:
The result of the product B2.
The result of the B2 collaborating with the (The result of the product A2.)
Builder
The builder pattern might seem similar to the abstract factory pattern but it creates an object step by step whereas the abstract factory pattern returns the object in one go.
from __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import Anyclass Builder(ABC):""" The Builder interface specifies methods for creating the different parts of the Product objects. """@property@abstractmethoddef product(self) ->None:pass@abstractmethoddef produce_part_a(self) ->None:pass@abstractmethoddef produce_part_b(self) ->None:pass@abstractmethoddef produce_part_c(self) ->None:passclass ConcreteBuilder1(Builder):""" The Concrete Builder classes follow the Builder interface and provide specific implementations of the building steps. Your program may have several variations of Builders, implemented differently. """def__init__(self) ->None:""" A fresh builder instance should contain a blank product object, which is used in further assembly. """self.reset()def reset(self) ->None:self._product = Product1()@propertydef product(self) -> Product1:""" Concrete Builders are supposed to provide their own methods for retrieving results. That's because various types of builders may create entirely different products that don't follow the same interface. Therefore, such methods cannot be declared in the base Builder interface (at least in a statically typed programming language). Usually, after returning the end result to the client, a builder instance is expected to be ready to start producing another product. That's why it's a usual practice to call the reset method at the end of the `getProduct` method body. However, this behavior is not mandatory, and you can make your builders wait for an explicit reset call from the client code before disposing of the previous result. """ product =self._productself.reset()return productdef produce_part_a(self) ->None:self._product.add("PartA1")def produce_part_b(self) ->None:self._product.add("PartB1")def produce_part_c(self) ->None:self._product.add("PartC1")class Product1():""" It makes sense to use the Builder pattern only when your products are quite complex and require extensive configuration. Unlike in other creational patterns, different concrete builders can produce unrelated products. In other words, results of various builders may not always follow the same interface. """def__init__(self) ->None:self.parts = []def add(self, part: Any) ->None:self.parts.append(part)def list_parts(self) ->None:print(f"Product parts: {', '.join(self.parts)}", end="")class Director:""" The Director is only responsible for executing the building steps in a particular sequence. It is helpful when producing products according to a specific order or configuration. Strictly speaking, the Director class is optional, since the client can control builders directly. """def__init__(self) ->None:self._builder =None@propertydef builder(self) -> Builder:returnself._builder@builder.setterdef builder(self, builder: Builder) ->None:""" The Director works with any builder instance that the client code passes to it. This way, the client code may alter the final type of the newly assembled product. """self._builder = builder""" The Director can construct several product variations using the same building steps. """def build_minimal_viable_product(self) ->None:self.builder.produce_part_a()def build_full_featured_product(self) ->None: # KEY POINTself.builder.produce_part_a()self.builder.produce_part_b()self.builder.produce_part_c()if__name__=="__main__":""" The client code creates a builder object, passes it to the director and then initiates the construction process. The end result is retrieved from the builder object. """ director = Director() builder = ConcreteBuilder1() director.builder = builderprint("Standard basic product: ") director.build_minimal_viable_product() builder.product.list_parts()print("\n")print("Standard full featured product: ") director.build_full_featured_product() builder.product.list_parts()print("\n")# Remember, the Builder pattern can be used without a Director class.print("Custom product: ") builder.produce_part_a() builder.produce_part_b() builder.product.list_parts()
Standard basic product:
Product parts: PartA1
Standard full featured product:
Product parts: PartA1, PartB1, PartC1
Custom product:
Product parts: PartA1, PartB1
Prototype
The prototype is used when we want to copy/clone the object. It python this is done using copy.copy (for shallow copy, i.e. objects are passed by reference) and copy.deepcopy (new object are created).
import copyclass SelfReferencingEntity:def__init__(self):self.parent =Nonedef set_parent(self, parent):self.parent = parentclass SomeComponent:""" Python provides its own interface of Prototype via `copy.copy` and `copy.deepcopy` functions. And any class that wants to implement custom implementations have to override `__copy__` and `__deepcopy__` member # KEY POINT functions. """def__init__(self, some_int, some_list_of_objects, some_circular_ref):self.some_int = some_intself.some_list_of_objects = some_list_of_objectsself.some_circular_ref = some_circular_refdef __copy__(self):""" Create a shallow copy. This method will be called whenever someone calls `copy.copy` with this object and the returned value is returned as the new shallow copy. """# First, let's create copies of the nested objects. some_list_of_objects = copy.copy(self.some_list_of_objects) some_circular_ref = copy.copy(self.some_circular_ref)# Then, let's clone the object itself, using the prepared clones of the# nested objects. new =self.__class__(self.some_int, some_list_of_objects, some_circular_ref ) new.__dict__.update(self.__dict__)return newdef __deepcopy__(self, memo=None):""" Create a deep copy. This method will be called whenever someone calls `copy.deepcopy` with this object and the returned value is returned as the new deep copy. What is the use of the argument `memo`? Memo is the dictionary that is used by the `deepcopy` library to prevent infinite recursive copies in instances of circular references. Pass it to all the `deepcopy` calls you make in the `__deepcopy__` implementation to prevent infinite recursions. """if memo isNone: memo = {}# First, let's create copies of the nested objects. some_list_of_objects = copy.deepcopy(self.some_list_of_objects, memo) some_circular_ref = copy.deepcopy(self.some_circular_ref, memo)# Then, let's clone the object itself, using the prepared clones of the# nested objects. new =self.__class__(self.some_int, some_list_of_objects, some_circular_ref ) new.__dict__ = copy.deepcopy(self.__dict__, memo)return newif__name__=="__main__": list_of_objects = [1, {1, 2, 3}, [1, 2, 3]] circular_ref = SelfReferencingEntity() component = SomeComponent(23, list_of_objects, circular_ref) circular_ref.set_parent(component) shallow_copied_component = copy.copy(component)# Let's change the list in shallow_copied_component and see if it changes in# component. shallow_copied_component.some_list_of_objects.append("another object")if component.some_list_of_objects[-1] =="another object":print("Adding elements to `shallow_copied_component`'s ""some_list_of_objects adds it to `component`'s ""some_list_of_objects." )else:print("Adding elements to `shallow_copied_component`'s ""some_list_of_objects doesn't add it to `component`'s ""some_list_of_objects." )# Let's change the set in the list of objects. component.some_list_of_objects[1].add(4)if4in shallow_copied_component.some_list_of_objects[1]:print("Changing objects in the `component`'s some_list_of_objects ""changes that object in `shallow_copied_component`'s ""some_list_of_objects." )else:print("Changing objects in the `component`'s some_list_of_objects ""doesn't change that object in `shallow_copied_component`'s ""some_list_of_objects." ) deep_copied_component = copy.deepcopy(component)# Let's change the list in deep_copied_component and see if it changes in# component. deep_copied_component.some_list_of_objects.append("one more object")if component.some_list_of_objects[-1] =="one more object":print("Adding elements to `deep_copied_component`'s ""some_list_of_objects adds it to `component`'s ""some_list_of_objects." )else:print("Adding elements to `deep_copied_component`'s ""some_list_of_objects doesn't add it to `component`'s ""some_list_of_objects." )# Let's change the set in the list of objects. component.some_list_of_objects[1].add(10)if10in deep_copied_component.some_list_of_objects[1]:print("Changing objects in the `component`'s some_list_of_objects ""changes that object in `deep_copied_component`'s ""some_list_of_objects." )else:print("Changing objects in the `component`'s some_list_of_objects ""doesn't change that object in `deep_copied_component`'s ""some_list_of_objects." )print(f"id(deep_copied_component.some_circular_ref.parent): "f"{id(deep_copied_component.some_circular_ref.parent)}" )print(f"id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent): "f"{id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent)}" )print("^^ This shows that deepcopied objects contain same reference, they ""are not cloned repeatedly." )
Adding elements to `shallow_copied_component`'s some_list_of_objects adds it to `component`'s some_list_of_objects.
Changing objects in the `component`'s some_list_of_objects changes that object in `shallow_copied_component`'s some_list_of_objects.
Adding elements to `deep_copied_component`'s some_list_of_objects doesn't add it to `component`'s some_list_of_objects.
Changing objects in the `component`'s some_list_of_objects doesn't change that object in `deep_copied_component`'s some_list_of_objects.
id(deep_copied_component.some_circular_ref.parent): 4532238608
id(deep_copied_component.some_circular_ref.parent.some_circular_ref.parent): 4532238608
^^ This shows that deepcopied objects contain same reference, they are not cloned repeatedly.
Singleton (thread-safe)
When we want to have only one instance of the object.
from threading import Lock, Threadclass SingletonMeta(type):""" This is a thread-safe implementation of Singleton. """ _instances = {} _lock: Lock = Lock()""" We now have a lock object that will be used to synchronize threads during first access to the Singleton. """def__call__(cls, *args, **kwargs):""" Possible changes to the value of the `__init__` argument do not affect the returned instance. """# Now, imagine that the program has just been launched. Since there's no# Singleton instance yet, multiple threads can simultaneously pass the# previous conditional and reach this point almost at the same time. The# first of them will acquire lock and will proceed further, while the# rest will wait here.with cls._lock:# The first thread to acquire the lock, reaches this conditional,# goes inside and creates the Singleton instance. Once it leaves the# lock block, a thread that might have been waiting for the lock# release may then enter this section. But since the Singleton field# is already initialized, the thread won't create a new object.if cls notin cls._instances: # KEY POINT instance =super().__call__(*args, **kwargs) cls._instances[cls] = instancereturn cls._instances[cls]class Singleton(metaclass=SingletonMeta): value: str=None""" We'll use this property to prove that our Singleton really works. """def__init__(self, value: str) ->None:self.value = valuedef some_business_logic(self):""" Finally, any singleton should define some business logic, which can be executed on its instance. """def test_singleton(value: str) ->None: singleton = Singleton(value)print(singleton.value)if__name__=="__main__":# The client code.print("If you see the same value, then singleton was reused (yay!)\n""If you see different values, ""then 2 singletons were created (booo!!)\n\n""RESULT:\n") process1 = Thread(target=test_singleton, args=("FOO",)) process2 = Thread(target=test_singleton, args=("BAR",)) process1.start() process2.start()
If you see the same value, then singleton was reused (yay!)
If you see different values, then 2 singletons were created (booo!!)
RESULT:
FOO
FOO