Creational Design Patterns

Published on
10 mins read
347 views

In this document, we'll explore six creational design patterns in Python: Singleton, Factory Method, Abstract Factory, Builder, Prototype, and Object Pool. These patterns are essential for managing object creation and initialization in a flexible and maintainable way.

Singleton Pattern:

What is it?

Imagine a magic potion that guarantees there's only one instance of a class. That's the Singleton pattern! It ensures that a class has only one instance and provides a global point of access to it.

Why use it?

Sometimes, you want to control access to a single resource, like a database connection or a global configuration, to prevent chaos in your code.

Tech Giant Example:

Consider a LoggingService in a tech giant's massive codebase. You'd want only one logger instance to avoid spamming log files or overloading servers. The Singleton pattern ensures this.

Example:

# Singleton Pattern

class ConfigurationManager:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(ConfigurationManager, cls).__new__(cls)
            cls._instance.config = {}
        return cls._instance

    def load_config(self, config_file):
        # Load configuration settings from a file (simplified)
        with open(config_file) as file:
            self.config = json.load(file)

Factory Method Pattern

Description: The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate.

Example:

What is it?

Think of it as a factory for creating objects. The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate.

Why use it?

When you want to delegate the responsibility of object creation to derived classes. It's like having a pizza factory where you decide whether you want a Margherita or Pepperoni pizza.

Practical Example:

Consider a large online retailer like Amazon. They have different factories (subclasses) for creating different types of products (objects), like electronics, books, or clothing.

Copy code

class Vehicle:
def drive(self):
pass

class Car(Vehicle):
def drive(self):
return "Driving a car"

class Bike(Vehicle):
def drive(self):
return "Riding a bike"

class VehicleFactory:
def create_vehicle(self):
pass

class CarFactory(VehicleFactory):
def create_vehicle(self):
return Car()

class BikeFactory(VehicleFactory):
def create_vehicle(self):
return Bike()

Abstract Factory Pattern

What is it?

Think of it as a super factory that creates families of related objects. The Abstract Factory pattern provides an interface for creating families of objects without specifying their concrete classes.

Why use it?

When you need to ensure that a group of objects work together seamlessly, like creating a UI for different operating systems (Windows, macOS, Linux).

Practical Example: - Let's imagine a car manufacturing company like Tesla. They have different factories (Abstract Factories) for creating electric car parts for different models.

Example:

class Button:
def render(self):
pass

class Checkbox:
def render(self):
pass

class WindowsButton(Button):
def render(self):
return "Windows button"

class WindowsCheckbox(Checkbox):
def render(self):
return "Windows checkbox"

class MacOSButton(Button):
def render(self):
return "macOS button"

class MacOSCheckbox(Checkbox):
def render(self):
return "macOS checkbox"

class UIFactory:
def create_button(self):
pass

    def create_checkbox(self):
        pass

class WindowsUIFactory(UIFactory):
def create_button(self):

Builder Pattern

The Builder pattern is a creational design pattern that is used to construct complex objects step by step. It separates the construction of an object from its representation, allowing the same construction process to create different representations.

The main idea behind the Builder pattern is to encapsulate the creation logic inside a separate class called the builder, which contains methods for building each part of the complex object. The builder class is typically a standalone class or an inner class within the main object class.

How It Works

Here's how the Builder pattern works:

  1. The client code creates an instance of the builder class.
  2. The client code calls the builder's methods to set the desired values for each property of the complex object.
  3. The client code calls the builder's build() method to retrieve the final constructed object.

Advantages

The main advantages of using the Builder pattern are:

  1. Improves readability, as the client code is more expressive and easier to understand.
  2. Allows for the creation of complex objects with optional parameters and different configurations.
  3. Supports the creation of immutable objects, ensuring their consistency and thread safety.
  4. Promotes separation of concerns, as the object's construction code is encapsulated in the builder class.

Example

Here's a simple example to illustrate the Builder pattern. Let's say we have a class called Car with properties such as model, color, year, and mileage. We can use the Builder pattern to construct a Car object step by step.

public class Car {
    private String model;
    private String color;
    private int year;
    private int mileage;

    public static class Builder {
        private String model;
        private String color;
        private int year;
        private int mileage;

        public Builder setModel(String model) {
            this.model = model;
            return this;
        }

        public Builder setColor(String color) {
            this.color = color;
            return this;
        }

        public Builder setYear(int year) {
            this.year = year;
            return this;
        }

        public Builder setMileage(int mileage) {
            this.mileage = mileage;
            return this;
        }

        public Car build() {
            Car car = new Car();
            car.model = this.model;
            car.color = this.color;
            car.year = this.year;
            car.mileage = this.mileage;
            return car;
        }
    }

    // Getters and setters

    public static void main(String[] args) {
        Car car = new Car.Builder()
            .setModel("Tesla Model S")
            .setColor("Black")
            .setYear(2021)
            .setMileage(1000)
            .build();

        System.out.println(car.getModel());
        System.out.println(car.getColor());
        System.out.println(car.getYear());
        System.out.println(car.getMileage());
    }
}

Prototype pattern

The Prototype pattern is a creational design pattern that allows objects to create duplicate copies of themselves. It provides a way to create new objects by cloning existing ones, without requiring knowledge of their concrete classes.

The key idea behind the Prototype pattern is that an object, known as the prototype, serves as a blueprint for creating new objects. Instead of using costly object creation mechanisms like new, the prototype is cloned to create new instances. This cloning process can be deep or shallow, depending on whether the object's references are also cloned.

Here's how the Prototype pattern works:

  1. The client code obtains a reference to a prototype object.
  2. The client code calls a method on the prototype to create a new object.
  3. The prototype object is cloned, creating a new instance with identical state.
  4. The client code can modify the cloned object if necessary and use it.

By using the Prototype pattern, we can avoid the overhead of creating objects from scratch, especially if the construction process is complex or time-consuming. It allows us to create new objects by simply cloning existing ones, which can be more efficient and flexible.

The Prototype pattern also promotes decoupling between the client code and the concrete classes of objects being created. The client code only needs to work with the prototype interface, without needing to know or depend on the concrete implementation.

An example of the Prototype pattern could be a graphics application that allows users to create complex shapes. Rather than creating new shape objects from scratch, the application can use a prototype shape object as a blueprint.

interface Shape {
    void draw();
    Shape clone();
}

class Circle implements Shape {
    private int x;
    private int y;
    private int radius;

    public Circle(int x, int y, int radius) {
        this.x = x;
        this.y = y;
        this.radius = radius;
    }

    public void draw() {
        System.out.println("Drawing a circle at (" + x + "," + y + ") with radius " + radius);
    }

    public Shape clone() {
        return new Circle(this.x, this.y, this.radius);
    }
}

class Application {
    private Shape circlePrototype;

    public Application() {
        // Create a prototype circle object
        circlePrototype = new Circle(0, 0, 10);
    }

    public void createCircle(int x, int y, int radius) {
        // Clone the prototype and customize the clone
        Shape circle = circlePrototype.clone();
        ((Circle) circle).setX(x);
        ((Circle) circle).setY(y);
        ((Circle) circle).setRadius(radius);

        // Use the cloned circle
        circle.draw();
    }
}

In this example, we have an Application class that manages the creation of circles using the Prototype pattern. It maintains a prototype circle object and clones it when needed. The createCircle() method demonstrates the usage of the prototype to create customized circles.

By cloning the prototype, the application can avoid the overhead of creating new circle objects from scratch. The cloned circle can be customized and used as needed. This approach is more efficient and flexible compared to creating new circle objects using new.

The Prototype pattern is particularly useful when the cost of creating new objects is high, or when objects need to be customized based on certain parameters before using them.

Object Pool Pattern

The object pool pattern is a software design pattern that manages a set of objects, called a pool, so that they can be reused. This pattern can be used to improve the performance of an application by reducing the number of times objects need to be created and destroyed.

What is it?

The object pool pattern works by creating a pool of objects that are pre-initialized. When a client requests an object from the pool, the pool returns an object from the pool. If the pool is empty, the pool creates a new object and returns it to the client. When the client is finished with the object, it returns the object to the pool.

Why use it?

The object pool pattern can be used to improve the performance of an application in the following situations:

  • When the cost of creating an object is high: The object pool pattern can avoid the cost of creating an object by reusing objects from the pool.
  • When the objects are expensive to store: The object pool pattern can reduce the amount of memory used by an application by reusing objects from the pool.
  • When the objects are frequently used: The object pool pattern can improve the performance of an application by reducing the time it takes to create an object.

Example

Database connections: A database connection is an object that represents a connection to a database server. The cost of creating a database connection can be high, especially if the database server is remote. The object pool pattern can be used to pool database connections, so that they can be reused. This can improve the performance of an application by reducing the number of times database connections need to be created and destroyed.

Network sockets: A network socket is an object that represents a connection to another computer. The cost of creating a network socket can be high, especially if the other computer is on a remote network. The object pool pattern can be used to pool network sockets, so that they can be reused. This can improve the performance of an application by reducing the number of times network sockets need to be created and destroyed.

Graphics objects: A graphics object is an object that represents a graphical element, such as a line, a circle, or a text box. The cost of creating a graphics object can be high, especially if the graphics object is complex. The object pool pattern can be used to pool graphics objects, so that they can be reused. This can improve the performance of an application by reducing the number of times graphics objects need to be created and destroyed.