Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Creational Patterns


Table of Contents


Singleton

πŸ“– Definition

Singleton Pattern is a creational design pattern that guarantees a class has only one instance and provides a global point of access to it.

Two requirements define the pattern:

  1. Single instance: No matter how many times any part of the code requests it, the same object is returned.
  2. Global access: Any component can reach the instance without needing it passed through constructors or method parameters.

Singleton is useful in scenarios like:

  • Managing Shared Resources (database connections, thread pools, caches, configuration settings)
  • Coordinating System-Wide Actions (logging, print spoolers, file managers)
  • Managing State (user session, application state)

🧩 Class Diagram

To implement the singleton pattern, we must prevent external objects from creating instances of the singleton class. Only the singleton class should be permitted to create its own objects.

Additionally, we need to provide a method for external objects to access the singleton object.

Singleton Class Diagram

  • An instance field stores the one and only Singleton object.
  • The constructor is private or otherwise restricted, so other code cannot create new instances directly.
  • A getInstance() (or similar) class-level method returns the shared instance and is accessible from anywhere.

πŸ›  Implementation

class Singleton {
    // Holds the single shared instance (initially not created)
    private static Singleton instance;

    // Private constructor prevents creating objects from outside the class
    private Singleton() {}

    // Global access point to get the Singleton instance
    public static Singleton getInstance() {

        // Create the instance only when first requested (initialization)
        if (instance == null) {
            instance = new Singleton();
        }

        // Return the shared instance
        return instance;
    }
}

Factory Method

πŸ“– Definition

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

It’s particularly useful in situations where:

  • The exact type of object to be created isn’t known until runtime.
  • Object creation logic is complex, repetitive, or needs encapsulation.
  • You want to follow the Open/Closed Principle, open for extension, closed for modification.

🧩 Class Diagram

Factory Method Class Diagram

  • Product: The interface or abstract class that defines the contract for all objects the factory method creates. Every concrete product implements this interface, which means the rest of the system can work with any product without knowing its concrete type.

  • ConcreteProduct: The actual classes that implement the Product interface. Each one provides its own behavior.

  • Creator: An abstract class (or an interface) that declares the factory method, which returns an object of type Product.

  • ConcreteCreator: Subclasses of Creator that override the factory method to return a specific ConcreteProduct. Each creator is paired with exactly one product type.

πŸ›  Implementation

  1. Define the Product Interface
interface Notification {
    public void send(String message);
}
  1. Define Concrete Products
class EmailNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Sending email: " + message);
    }
}

class SMSNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Sending SMS: " + message);
    }
}

class PushNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Sending push notification: " + message);
    }
}

class SlackNotification implements Notification {
    @Override
    public void send(String message) {
        System.out.println("Sending Slack message: " + message);
    }
}
  1. Define Abstract Creator
abstract class NotificationCreator {
    // Factory Method - subclasses decide what to create
    public abstract Notification createNotification();

    // Shared logic that uses the factory method
    public void send(String message) {
        Notification notification = createNotification();
        notification.send(message);
    }
}
  1. Define Concrete Creators
class EmailNotificationCreator extends NotificationCreator {
    @Override
    public Notification createNotification() {
        return new EmailNotification();
    }
}

class SMSNotificationCreator extends NotificationCreator {
    @Override
    public Notification createNotification() {
        return new SMSNotification();
    }
}

class PushNotificationCreator extends NotificationCreator {
    @Override
    public Notification createNotification() {
        return new PushNotification();
    }
}

class SlackNotificationCreator extends NotificationCreator {
    @Override
    public Notification createNotification() {
        return new SlackNotification();
    }
}
  1. Client Code
public class FactoryMethodDemo {
    public static void main(String[] args) {
        NotificationCreator creator;

        // Send Email
        creator = new EmailNotificationCreator();
        creator.send("Welcome to our platform!");

        // Send SMS
        creator = new SMSNotificationCreator();
        creator.send("Your OTP is 123456");

        // Send Push Notification
        creator = new PushNotificationCreator();
        creator.send("You have a new follower!");

        // Send Slack Message
        creator = new SlackNotificationCreator();
        creator.send("Standup in 10 minutes!");
    }
}

Abstract Factory

πŸ“– Definition

The Abstract Factory Design Pattern is a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

It’s particularly useful in situations where:

  • You need to create objects that must be used together and are part of a consistent family (e.g., GUI elements like buttons, checkboxes, and menus).
  • Your system must support multiple configurations, environments, or product variants (e.g., light vs. dark themes, Windows vs. macOS look-and-feel).
  • You want to enforce consistency across related objects, ensuring that they are all created from the same factory.

🧩 Class Diagram

Abstract Factory Class Diagram

  • Abstract Factory

    • Defines a common interface for creating a family of related products.
    • Typically includes factory methods like createButton(), createCheckbox(), createTextField(), etc.
    • Clients rely on this interface to create objects without knowing their concrete types.
  • Concrete Factory

    • Implement the abstract factory interface.
    • Create concrete product variants that belong to a specific family or platform.
    • Each factory ensures that all components it produces are compatible (i.e., belong to the same platform/theme).
  • Abstract Product

    • Define the interfaces or abstract classes for a set of related components.
    • All product variants for a given type (e.g., WindowsButton, MacOSButton) will implement these interfaces.
  • Concrete Product

    • Implement the abstract product interfaces.
    • Contain platform-specific logic and appearance for the components.
  • Client

    • Uses the abstract factory and abstract product interfaces.
    • Is completely unaware of the concrete classes it is using β€” it only interacts with the factory and product interfaces.
    • Can switch entire product families (e.g., from Windows to macOS) by changing the factory without touching UI logic.

πŸ›  Implementation

  1. Define Abstract Product Interfaces
  • Button
interface Button {
    void paint();
    void onClick();
}
  • Checkbox
interface Checkbox {
    void paint();
    void onSelect();
}
  1. Create Concrete Products
  • Windows Products
class WindowsButton implements Button {
    @Override
    public void paint() {
        System.out.println("Painting a Windows-style button.");
    }

    @Override
    public void onClick() {
        System.out.println("Windows button clicked.");
    }
}

class WindowsCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("Painting a Windows-style checkbox.");
    }

    @Override
    public void onSelect() {
        System.out.println("Windows checkbox selected.");
    }
}
  • MacOS Products
class MacOSButton implements Button {
    @Override
    public void paint() {
        System.out.println("Painting a macOS-style button.");
    }

    @Override
    public void onClick() {
        System.out.println("macOS button clicked.");
    }
}

class MacOSCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("Painting a macOS-style checkbox.");
    }

    @Override
    public void onSelect() {
        System.out.println("macOS checkbox selected.");
    }
}
  1. Define the Abstract Factory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}
  1. Implement Concrete Factories
  • WindowsFactory
class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}
  • MacOSFactory
class MacOSFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}
  1. Client Code
class Application {
    private final Button button;
    private final Checkbox checkbox;

    public Application(GUIFactory factory) {
        this.button = factory.createButton();
        this.checkbox = factory.createCheckbox();
    }

    public void renderUI() {
        button.paint();
        checkbox.paint();
    }
}
  1. Wire Everything Together
public class AppLauncher {
    public static void main(String[] args) {
        // Simulate platform detection
        String os = System.getProperty("os.name");
        GUIFactory factory;

        if (os.contains("Windows")) {
            factory = new WindowsFactory();
        } else {
            factory = new MacOSFactory();
        }

        Application app = new Application(factory);
        app.renderUI();
    }
}
  • Output (on MacOS)
Painting a macOS-style button.
Painting a macOS-style checkbox.
  • Output (on Windows)
Painting a Windows-style button.
Painting a Windows-style checkbox.

Builder

πŸ“– Definition

The Builder Design Pattern is a creational pattern that lets you construct complex objects step-by-step, separating the construction logic from the final representation.

Two ideas define the pattern:

  1. Step-by-step construction: Instead of passing everything to a constructor at once, you set each field through individual method calls. You only call the methods for the fields you need.
  2. Fluent interface: Each setter method returns the builder itself, allowing you to chain calls into a single readable expression that ends with build().

🧩 Class Diagram

The Builder pattern involves four participants. In many real-world implementations, the Director is optional and is often skipped when using fluent builders.

Builder Class Diagram

  • Builder

    • Exposes methods to configure the product step by step.
    • Typically returns the builder itself from each method to enable fluent chaining.
    • Often implemented as a static nested class inside the product class.
  • ConcreteBuilder

    • Implements the builder API (either via an interface or directly through fluent methods).
    • Stores intermediate state for the object being constructed.
    • Implements build() to validate inputs and produce the final product instance.
  • Product

    • The complex object being constructed.
    • Often immutable and created only through the builder.
    • Commonly has a private constructor that copies state from the builder.
  • Director (Optional)

    • Coordinates the construction process by calling builder steps in a specific sequence.
    • Useful when you want to encapsulate standard configurations or reusable construction sequences.
    • Often omitted in fluent builder style, where the client effectively plays this role by chaining builder calls.

πŸ›  Implementation

  1. Create the Product and Builder
class HttpRequest {
    // Required
    private final String url;

    // Optional
    private final String method;
    private final Map<String, String> headers;
    private final Map<String, String> queryParams;
    private final String body;
    private final int timeout;

    // Private constructor - only the Builder can call this
    private HttpRequest(Builder builder) {
        this.url = builder.url;
        this.method = builder.method;
        this.headers = Collections.unmodifiableMap(new HashMap<>(builder.headers));
        this.queryParams = Collections.unmodifiableMap(new HashMap<>(builder.queryParams));
        this.body = builder.body;
        this.timeout = builder.timeout;
    }

    public String getUrl() { return url; }
    public String getMethod() { return method; }
    public Map<String, String> getHeaders() { return headers; }
    public Map<String, String> getQueryParams() { return queryParams; }
    public String getBody() { return body; }
    public int getTimeout() { return timeout; }

    @Override
    public String toString() {
        return "HttpRequest{url='" + url + "', method='" + method +
               "', headers=" + headers + ", queryParams=" + queryParams +
               ", body='" + body + "', timeout=" + timeout + "}";
    }

    // Static nested Builder class
    public static class Builder {
        private final String url; // required
        private String method = "GET";
        private Map<String, String> headers = new HashMap<>();
        private Map<String, String> queryParams = new HashMap<>();
        private String body;
        private int timeout = 30000;

        public Builder(String url) {
            this.url = url;
        }

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

        public Builder addHeader(String key, String value) {
            this.headers.put(key, value);
            return this;
        }

        public Builder addQueryParam(String key, String value) {
            this.queryParams.put(key, value);
            return this;
        }

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

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

        public HttpRequest build() {
            return new HttpRequest(this);
        }
    }
}
  1. Using the Builder from Client Code
public class Main {
    public static void main(String[] args) {
        // Simple GET request - just the URL
        HttpRequest get = new HttpRequest.Builder("https://api.example.com/users")
                .build();

        // POST with body and custom timeout
        HttpRequest post = new HttpRequest.Builder("https://api.example.com/users")
                .method("POST")
                .addHeader("Content-Type", "application/json")
                .body("{\"name\":\"Alice\",\"email\":\"alice@example.com\"}")
                .timeout(5000)
                .build();

        // Authenticated PUT with query parameters
        HttpRequest put = new HttpRequest.Builder("https://api.example.com/config")
                .method("PUT")
                .addHeader("Authorization", "Bearer token123")
                .addHeader("Content-Type", "application/json")
                .addQueryParam("env", "production")
                .addQueryParam("version", "2")
                .body("{\"feature_flag\":true}")
                .timeout(10000)
                .build();
    }
}