design #patterns #algorithm #programinglang #software-engineering

Creational Design Patterns

Creational Design Patterns are a category of design patterns that deal with object creation mechanisms. They provide various ways to instantiate objects in a manner suitable for the given situation. The purpose of creational patterns is to control how objects are created, which allows for greater flexibility and reuse of code while hiding the complex creation logic from the client.

There are five primary creational design patterns:

  1. Singleton Pattern
  2. Factory Method Pattern
  3. Abstract Factory Pattern
  4. Builder Pattern
  5. Prototype Pattern

1. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global access point to it. This is useful when exactly one object is needed to coordinate actions across the system, such as a configuration manager or a logging service.

  • Key Points:

    • Controls the instantiation of the class and ensures that only one object is created.
    • Provides a global point of access to the instance.
  • Example:

    class Singleton:
        _instance = None
     
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(Singleton, cls).__new__(cls)
            return cls._instance
     
    s1 = Singleton()
    s2 = Singleton()
    print(s1 is s2)  # Output: True
  • Use Cases: Configuration managers, logging utilities, connection pools.

2. Factory Method Pattern

The Factory Method pattern defines an interface for creating an object but lets subclasses decide which class to instantiate. It promotes loose coupling by eliminating the need to specify the exact class that will be instantiated.

  • Key Points:

    • Provides a method in a base class that can be overridden to create objects of derived classes.
    • Allows the code to depend on an interface rather than a concrete class.
  • Example:

    from abc import ABC, abstractmethod
     
    # Abstract Product
    class Button(ABC):
        @abstractmethod
        def render(self):
            pass
     
    # Concrete Products
    class WindowsButton(Button):
        def render(self):
            print("Rendering a Windows button")
     
    class MacOSButton(Button):
        def render(self):
            print("Rendering a MacOS button")
     
    # Factory Method
    class Dialog(ABC):
        @abstractmethod
        def create_button(self) -> Button:
            pass
     
        def render_dialog(self):
            button = self.create_button()
            button.render()
     
    # Concrete Factories
    class WindowsDialog(Dialog):
        def create_button(self) -> Button:
            return WindowsButton()
     
    class MacOSDialog(Dialog):
        def create_button(self) -> Button:
            return MacOSButton()
     
    # Client code
    dialog = WindowsDialog()
    dialog.render_dialog()  # Output: Rendering a Windows button
  • Use Cases: GUI libraries, data access layers, serializers.

3. Abstract Factory Pattern

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It is often used when the system needs to be independent of how its products are created, composed, or represented.

  • Key Points:

    • Encapsulates a group of individual factories with a common interface.
    • Each factory produced by the abstract factory corresponds to a specific family of products.
  • Example:

    from abc import ABC, abstractmethod
     
    # Abstract Products
    class Chair(ABC):
        @abstractmethod
        def sit_on(self):
            pass
     
    class Sofa(ABC):
        @abstractmethod
        def lie_on(self):
            pass
     
    # Concrete Products
    class VictorianChair(Chair):
        def sit_on(self):
            print("Sitting on a Victorian chair")
     
    class ModernChair(Chair):
        def sit_on(self):
            print("Sitting on a Modern chair")
     
    class VictorianSofa(Sofa):
        def lie_on(self):
            print("Lying on a Victorian sofa")
     
    class ModernSofa(Sofa):
        def lie_on(self):
            print("Lying on a Modern sofa")
     
    # Abstract Factory
    class FurnitureFactory(ABC):
        @abstractmethod
        def create_chair(self) -> Chair:
            pass
     
        @abstractmethod
        def create_sofa(self) -> Sofa:
            pass
     
    # Concrete Factories
    class VictorianFurnitureFactory(FurnitureFactory):
        def create_chair(self) -> Chair:
            return VictorianChair()
     
        def create_sofa(self) -> Sofa:
            return VictorianSofa()
     
    class ModernFurnitureFactory(FurnitureFactory):
        def create_chair(self) -> Chair:
            return ModernChair()
     
        def create_sofa(self) -> Sofa:
            return ModernSofa()
     
    # Client code
    factory = VictorianFurnitureFactory()
    chair = factory.create_chair()
    chair.sit_on()  # Output: Sitting on a Victorian chair
  • Use Cases: GUI toolkits, database management systems that support multiple database vendors.

4. Builder Pattern

The Builder pattern separates the construction of a complex object from its representation. It allows constructing a complex object step-by-step and provides flexibility in the construction process.

  • Key Points:

    • Useful for creating objects with many optional parts or configurations.
    • Helps to avoid a complex constructor or numerous parameters.
  • Example:

    class Car:
        def __init__(self):
            self.engine = None
            self.color = None
     
    class CarBuilder:
        def __init__(self):
            self.car = Car()
     
        def add_engine(self, engine):
            self.car.engine = engine
            return self
     
        def paint(self, color):
            self.car.color = color
            return self
     
        def build(self):
            return self.car
     
    # Client code
    car_builder = CarBuilder()
    car = car_builder.add_engine("V8").paint("Red").build()
    print(car.engine)  # Output: V8
    print(car.color)   # Output: Red
  • Use Cases: Building complex objects with many configurations, assembling hierarchical object structures.

5. Prototype Pattern

The Prototype pattern allows creating new objects by copying an existing object, known as a prototype. It avoids the cost of creating objects from scratch and is especially useful when the object creation process is resource-intensive.

  • Key Points:

    • Enables object cloning.
    • Reduces the need to create complex objects repeatedly.
  • Example:

    import copy
     
    class Prototype:
        def __init__(self, value):
            self.value = value
     
        def clone(self):
            return copy.deepcopy(self)
     
    # Client code
    prototype = Prototype("Prototype object")
    clone = prototype.clone()
    print(clone.value)  # Output: Prototype object
  • Use Cases: Object cloning, managing default configurations or initial states.

Summary

Creational patterns abstract the instantiation process, enabling flexibility and reuse of code. They help in managing complex object creation scenarios, promoting code modularity and separation of concerns. Each creational pattern addresses different scenarios of object creation and has its own benefits and use cases, making them an essential part of software design and architecture.

For more detailed information, you can refer to the comprehensive discussions on creational patterns available on platforms like Refactoring Guru and the Gang of Four book.