Module A2 — Creational Patterns

Complete reference notes · Track A: LLD · Week 4

Singleton Factory Method Abstract Factory Builder Prototype
⚡ Interactive Visual Version ← Recommended for learning. This page is the printable reference.
Overview

Creational design patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented. As systems evolve, they often rely more on object composition than class inheritance, shifting the emphasis away from hard-coding a fixed set of behaviours toward defining a smaller set of fundamental behaviours that can be composed into any number of more complex ones. Thus, creating objects with specific behaviours requires more than simply instantiating a class.

1. Singleton

Definition: Ensure a class has only one instance, and provide a global point of access to it.

When to Use:

Implementation: Enum Singleton (Preferred in Java)

Joshua Bloch (Effective Java) recommends the Enum approach. It provides built-in serialization machinery, guarantees against multiple instantiations, and handles reflection attacks perfectly.

public enum ConfigManager {
    INSTANCE;

    private Map<String, String> properties;

    ConfigManager() {
        properties = new HashMap<>(); // load from file
    }

    public String getProperty(String key) {
        return properties.get(key);
    }
}

Implementation: Double-Checked Locking (Thread-Safe)

If you explicitly need lazy initialization and cannot use Enums.

public class DatabasePool {
    private static volatile DatabasePool instance;

    private DatabasePool() { 
        // private constructor prevents instantiation
    }

    public static DatabasePool getInstance() {
        if (instance == null) { // 1st check (no lock, fast-path)
            synchronized (DatabasePool.class) {
                if (instance == null) { // 2nd check (safe)
                    instance = new DatabasePool();
                }
            }
        }
        return instance;
    }
}

Note: The volatile keyword is crucial. It ensures that multiple threads handle the instance variable correctly when it is being initialized.

SOLID Impact: Singleton

Violates SRP: The class manages its own lifecycle AND performs its primary business logic.
Recommendation: In modern architectures (e.g., Spring framework), use Dependency Injection containers to manage the "singleton" scope of an object, rather than hardcoding the Singleton pattern structurally.


2. Factory Method

Definition: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

When to Use:

Real-world analogy: A logistics company has a TransportBuilder. Its subclasses RoadLogistics and SeaLogistics decide whether to create a Truck or a Ship.

Implementation Example

// Product Interface
interface Notification {
    void send(String message);
}

// Concrete Products
class EmailNotification implements Notification {
    public void send(String msg) { System.out.println("Emailing: " + msg); }
}
class PushNotification implements Notification {
    public void send(String msg) { System.out.println("Pushing: " + msg); }
}

// Creator (The Factory)
abstract class NotificationCreator {
    public abstract Notification createNotification(); // Factory Method
    
    // Core business logic relying on the product
    public void broadcast(String msg) {
        Notification notification = createNotification();
        notification.send(msg);
    }
}

// Concrete Creators
class EmailCreator extends NotificationCreator {
    public Notification createNotification() { return new EmailNotification(); }
}
class PushCreator extends NotificationCreator {
    public Notification createNotification() { return new PushNotification(); }
}

3. Abstract Factory

Definition: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Difference from Factory Method:

When to Use:

Implementation Example

// Abstract Factory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// Concrete Factory 1: Mac
class MacFactory implements GUIFactory {
    public Button createButton() { return new MacButton(); }
    public Checkbox createCheckbox() { return new MacCheckbox(); }
}

// Concrete Factory 2: Win
class WinFactory implements GUIFactory {
    public Button createButton() { return new WinButton(); }
    public Checkbox createCheckbox() { return new WinCheckbox(); }
}

// Client Code: Injects the factory
class Application {
    private Button button;
    public Application(GUIFactory factory) {
        button = factory.createButton(); // Client doesn't care if it's Mac or Win
    }
}

4. Builder

Definition: Separate the construction of a complex object from its representation so that the same construction process can create different representations.

When to Use:

Implementation

public class UserProfile {
    // Final fields make the object immutable
    private final String firstName; // Required
    private final String lastName;  // Required
    private final int age;          // Optional
    private final String phone;     // Optional
    private final String address;   // Optional

    private UserProfile(Builder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public static class Builder {
        private final String firstName;
        private final String lastName;
        private int age = 0;              // Default optional
        private String phone = "";        // Default optional
        private String address = "";      // Default optional

        public Builder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

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

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

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

        public UserProfile build() {
            // Validation logic goes here before object creation
            if(age < 0) throw new IllegalArgumentException("Age cannot be negative");
            return new UserProfile(this);
        }
    }
}

// Usage:
UserProfile user = new UserProfile.Builder("Ajay", "Dev")
                        .age(28)
                        .address("123 Tech Lane")
                        .build();

5. Prototype

Definition: Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

When to Use:

Implementation (Deep vs Shallow Clone)

Java’s default clone() method provides a shallow copy (references to nested objects are shared). In System Design, you usually want a deep copy, executed manually via a Copy Constructor.

abstract class Shape {
    public int x, y;
    public String color;

    // Copy constructor
    public Shape(Shape target) {
        if (target != null) {
            this.x = target.x;
            this.y = target.y;
            this.color = target.color;
        }
    }

    public abstract Shape clone();
}

class Circle extends Shape {
    public int radius;

    public Circle(Circle target) {
        super(target); // Copy parent properties
        if (target != null) {
            this.radius = target.radius;
        }
    }

    @Override
    public Shape clone() {
        return new Circle(this); // Passes itself to copy constructor
    }
}

// Registry used to cache prototypes
class ShapeRegistry {
    private Map<String, Shape> cache = new HashMap<>();

    public ShapeRegistry() {
        Circle circle = new Circle(null);
        circle.x = 10; circle.y = 10; circle.radius = 20; circle.color = "Red";
        cache.put("Big Red Circle", circle);
    } // Create expensive object ONCE

    public Shape get(String key) {
        return cache.get(key).clone(); // Return cloned instances cheaply
    }
}

Comparison Summary Table

Pattern Creates Mechanism Primary SOLID Principle Enforced
Singleton A single, globally accessible instance Private constructor + static accessor SRP (Though it often violates it practically, conceptually it manages one state globally).
Factory Method One specific product object Subclass overrides a creator method OCP (Add new creators without modifying existing ones).
Abstract Factory A family of related product objects Interface injection with multiple factory methods OCP and ISP (Interface Segregation).
Builder A complex object, step-by-step Inner Builder class, chained setters, `build()` method SRP (Separates construction logic from the data model).
Prototype A clone of an existing object `clone()` interfaces and copy constructors OCP (Cloning avoids concrete dependencies on classes).

⚡ Open Interactive Version ↑ Back to Roadmap