Creational Design Patterns in Software Development

Published on 16 April 2024
6 min read
Design Patterns
Software Development
Programming
Python
Tutorial
Creational Design Patterns in Software Development

Introduction

Creational design patterns provide various ways to instantiate objects while abstracting the instantiation process. They lead to more flexible and reusable code, making it easier to design robust and maintainable software systems. This post will cover the Singleton, Prototype, Factory Method, Builder, and Abstract Factory patterns.

Singleton Pattern

Introduction

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This pattern is often used in scenarios where exactly one object is needed to coordinate actions across the system.

Definition

The Singleton pattern restricts the instantiation of a class to one single instance and provides a global access point to that instance.

Use Cases

  • Logging
  • Configuration settings
  • Managing connections to a database
  • Caching

Benefits

  • Controlled access to the sole instance
  • Reduced namespace pollution
  • Permits refinement of operations and representation
  • Provides a single point of access

Examples

python
class Singleton:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

singleton1 = Singleton()
singleton2 = Singleton()

print(singleton1 is singleton2)  # Output: True

Real-world Applications

In web applications, the Singleton pattern is often used for database connection pooling. For example, a web server may need a single database connection pool to manage access to the database, ensuring efficient resource utilization and connection management.

Prototype Pattern

Introduction

The Prototype pattern is used to create a duplicate object while keeping performance in mind. This pattern involves implementing a prototype interface which tells to create a clone of the current object.

Definition

The Prototype pattern is a creational design pattern in which new objects are created by copying an existing object, known as the prototype.

Use Cases

  • When the type of objects to create is determined by a prototypical instance
  • To avoid the overhead of creating new objects from scratch
  • When creating an object is costly or complex

Benefits

  • Reduces the need for creating subclasses
  • Speeds up the instantiation process
  • Simplifies the creation of complex objects

Examples

python
import copy

class Prototype:
    def __init__(self, value):
        self.value = value

    def clone(self):
        return copy.deepcopy(self)

prototype1 = Prototype([1, 2, 3])
prototype2 = prototype1.clone()
prototype2.value.append(4)

print(prototype1.value)  # Output: [1, 2, 3]
print(prototype2.value)  # Output: [1, 2, 3, 4]

Real-world Applications

In game development, the Prototype pattern is used to create different game characters or objects dynamically. For instance, a game might have a prototypical enemy object that can be cloned and customized for different enemy types.

Factory Method Pattern

Introduction

The Factory Method pattern defines an interface for creating an object but allows subclasses to alter the type of objects that will be created. This pattern lets a class defer instantiation to subclasses.

Definition

The Factory Method pattern is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created.

Use Cases

  • When a class cannot anticipate the type of objects it needs to create
  • To allow subclasses to provide specific implementations
  • To delegate the responsibility of instantiation to subclasses

Benefits

  • Promotes loose coupling
  • Enhances code reusability
  • Facilitates the addition of new products without modifying existing code

Examples

python
from abc import ABC, abstractmethod

class Product(ABC):
    @abstractmethod
    def operation(self) -> str:
        pass

class ConcreteProductA(Product):
    def operation(self) -> str:
        return "Result of ConcreteProductA"

class ConcreteProductB(Product):
    def operation(self) -> str:
        return "Result of ConcreteProductB"

class Creator(ABC):
    @abstractmethod
    def factory_method(self) -> Product:
        pass

    def some_operation(self) -> str:
        product = self.factory_method()
        return f"Creator: The same creator's code has just worked with {product.operation()}"

class ConcreteCreatorA(Creator):
    def factory_method(self) -> Product:
        return ConcreteProductA()

class ConcreteCreatorB(Creator):
    def factory_method(self) -> Product:
        return ConcreteProductB()

creator_a = ConcreteCreatorA()
print(creator_a.some_operation())  # Output: Creator: The same creator's code has just worked with Result of ConcreteProductA

creator_b = ConcreteCreatorB()
print(creator_b.some_operation())  # Output: Creator: The same creator's code has just worked with Result of ConcreteProductB

Real-world Applications

In web development frameworks, the Factory Method pattern is often used to create objects like controllers, services, or handlers based on configuration or user input.

Builder Pattern

Introduction

The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Definition

The Builder pattern is a creational design pattern that lets you construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

Use Cases

  • When an object requires numerous steps to be created
  • To construct composite objects
  • To improve readability and maintainability of the code

Benefits

  • Allows for more control over the construction process
  • Enhances code readability and maintainability
  • Supports the creation of different representations of an object

Examples

python
class House:
    def __init__(self):
        self.rooms = []
        self.floors = 0
        self.has_garage = False

    def __str__(self):
        return f"House with {self.floors} floors, {'a garage' if self.has_garage else 'no garage'}, and rooms: {', '.join(self.rooms)}"

class HouseBuilder:
    def __init__(self):
        self.house = House()

    def add_room(self, room):
        self.house.rooms.append(room)
        return self

    def set_floors(self, floors):
        self.house.floors = floors
        return self

    def add_garage(self):
        self.house.has_garage = True
        return self

    def build(self):
        return self.house

builder = HouseBuilder()
house = builder.set_floors(2).add_room("Kitchen").add_room("Bedroom").add_garage().build()
print(house)  # Output: House with 2 floors, a garage, and rooms: Kitchen, Bedroom

Real-world Applications

The Builder pattern is used in many modern development frameworks to construct complex objects such as UI components, configuration objects, or even SQL queries.

Abstract Factory Pattern

Introduction

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Definition

The Abstract Factory pattern is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

Use Cases

  • When a system needs to be independent of how its objects are created
  • To manage and organize a group of related or dependent objects
  • When families of related objects need to be used together

Benefits

  • Promotes consistency among products
  • Isolates concrete classes
  • Facilitates the exchange of product families
  • Supports the addition of new products

Examples

python
from abc import ABC, abstractmethod

class AbstractFactory(ABC):
    @abstractmethod
    def create_product_a(self):
        pass

    @abstractmethod
    def create_product_b(self):
        pass

class ConcreteFactory1(AbstractFactory):
    def create_product_a(self):
        return ProductA1()

    def create_product_b(self):
        return ProductB1()

class ConcreteFactory2(AbstractFactory):
    def create_product_a(self):
        return ProductA2()

    def create_product_b(self):
        return ProductB2()

class ProductA1:
    def useful_function_a(self) -> str:
        return "The result of the product A1."

class ProductA2:
    def useful_function_a(self) -> str:
        return "The result of the product A2."

class ProductB1:
    def useful_function_b(self) -> str:
        return "The result of the product B1."

class ProductB2:
    def useful_function_b(self) -> str:
        return "The result of the product B2."

def client_code(factory: AbstractFactory) -> None:
    product_a = factory.create

_product_a()
    product_b = factory.create_product_b()

    print(product_b.useful_function_b())
    print(product_a.useful_function_a())

client_code(ConcreteFactory1())
# Output:
# The result of the product B1.
# The result of the product A1.

client_code(ConcreteFactory2())
# Output:
# The result of the product B2.
# The result of the product A2.

Real-world Applications

Abstract Factory patterns are commonly used in GUI toolkits to create families of related objects such as buttons, checkboxes, and windows, ensuring consistency across different platforms.

Conclusion

Creational design patterns provide various ways to instantiate objects while abstracting the instantiation process, leading to more flexible and reusable code. By understanding and applying these patterns, developers can design more robust and maintainable software systems.

Powered by SvelteKit. Icons by Iconoir.