Home

Design Patterns


Design patterns are reusable, proven solutions to common problems in software design. They’re not finished code you copy-paste, but general templates or best practices that help you structure your code in a clean, flexible, and maintainable way.

Behavioural Patterns

Strategy Pattern


The Strategy pattern is a behavioral design pattern that lets you choose an algorithm (behavior) at runtime instead of hardcoding it.

Instead of writing one big class with lots of if/else logic, you:

interface PaymentStrategy {
    void pay(int amount);
}

class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card");
    }
}

class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal");
    }
}

class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

Observer Pattern


The Observer pattern is a behavioral design pattern where one object (the subject) keeps track of multiple dependent objects (observers) and automatically notifies them when its state changes.

interface Observer {
    void update(String message);
}

class EmailSubscriber implements Observer {
    public void update(String message) {
        System.out.println("Email received: " + message);
    }
}

class SMSSubscriber implements Observer {
    public void update(String message) {
        System.out.println("SMS received: " + message);
    }
}

class YouTubeChannel {
    private List<Observer> subscribers = new ArrayList<>();

    public void subscribe(Observer o) {
        subscribers.add(o);
    }

    public void unsubscribe(Observer o) {
        subscribers.remove(o);
    }

    public void uploadVideo(String title) {
        notifySubscribers(title);
    }

    private void notifySubscribers(String message) {
        for (Observer o : subscribers) {
            o.update(message);
        }
    }
}

Creational Patterns

Factory Method

The Factory method is a creational design pattern that provides a way to create objects without exposing the exact class being instantiated.

interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() {
        System.out.println("Drawing a Circle");
    }
}

class Rectangle implements Shape {
    public void draw() {
        System.out.println("Drawing a Rectangle");
    }
}

class Square implements Shape {
    public void draw() {
        System.out.println("Drawing a Square");
    }
}

class ShapeFactory {

    public static Shape getShape(String type) {
        if (type.equalsIgnoreCase("circle")) {
            return new Circle();
        } else if (type.equalsIgnoreCase("rectangle")) {
            return new Rectangle();
        } else if (type.equalsIgnoreCase("square")) {
            return new Square();
        }

        throw new IllegalArgumentException("Unknown shape type");
    }
}

Abstract Factory Pattern

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

Think of it as a factory of factories.

interface Chair {
    void sitOn();
}

interface Table {
    void use();
}

class ModernChair implements Chair {
    public void sitOn() {
        System.out.println("Sitting on a Modern Chair");
    }
}

class VictorianChair implements Chair {
    public void sitOn() {
        System.out.println("Sitting on a Victorian Chair");
    }
}

class ModernTable implements Table {
    public void use() {
        System.out.println("Using a Modern Table");
    }
}

class VictorianTable implements Table {
    public void use() {
        System.out.println("Using a Victorian Table");
    }
}

interface FurnitureFactory {
    Chair createChair();
    Table createTable();
}

class ModernFurnitureFactory implements FurnitureFactory {
    public Chair createChair() {
        return new ModernChair();
    }

    public Table createTable() {
        return new ModernTable();
    }
}

class VictorianFurnitureFactory implements FurnitureFactory {
    public Chair createChair() {
        return new VictorianChair();
    }

    public Table createTable() {
        return new VictorianTable();
    }
}

Singleton Pattern


The Singleton pattern ensures that a class has only one instance and provides a global access point to it.

class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Structural Pattern

Composite Pattern

The Composite pattern is a structural design pattern that lets you treat individual objects and groups of objects the same way.

interface FileSystemItem {
    void show();
}

// Leaf (file)
class File implements FileSystemItem {
    private String name;

    public File(String name) {
        this.name = name;
    }

    public void show() {
        System.out.println("File: " + name);
    }
}

// Composite (folder)
class Folder implements FileSystemItem {
    private String name;
    private List<FileSystemItem> items = new ArrayList<>();

    public Folder(String name) {
        this.name = name;
    }

    public void add(FileSystemItem item) {
        items.add(item);
    }

    public void show() {
        System.out.println("Folder: " + name);
        for (FileSystemItem item : items) {
            item.show(); // recursive call
        }
    }
}

The Factory method can also be used alongside the Composite pattern to simplify the creation of components in a tree structure, especially when different types of objects (e.g., leaf and composite nodes) need to be instantiated dynamically.”

class FileSystemFactory {

    public static FileSystemItem create(JSONObject json) {
        String type = json.getString("type");
        String name = json.getString("name");

        if (type.equals("file")) {
            return new File(name);
        }

        if (type.equals("folder")) {
            Folder folder = new Folder(name);

            JSONArray children = json.optJSONArray("children");
            if (children != null) {
                for (int i = 0; i < children.length(); i++) {
                    JSONObject childJson = children.getJSONObject(i);
                    folder.add(create(childJson)); // recursion
                }
            }

            return folder;
        }

        throw new IllegalArgumentException("Unknown type: " + type);
    }

public class Main {
    public static void main(String[] args) {
        String jsonStr = """
        {
          "type": "folder",
          "name": "root",
          "children": [
            { "type": "file", "name": "a.txt" },
            {
              "type": "folder",
              "name": "docs",
              "children": [
                { "type": "file", "name": "b.txt" }
              ]
            }
          ]
        }
        """;

        JSONObject json = new JSONObject(jsonStr);

        FileSystemItem root = FileSystemFactory.create(json);
        root.show("");
    }
}
}

Decorator Pattern


The Decorator pattern is a structural design pattern that lets you add new behavior to an object dynamically without changing its code.

interface Coffee {
    double cost();
    String description();
}

class BasicCoffee implements Coffee {
    public double cost() {
        return 5.0;
    }

    public String description() {
        return "Basic Coffee";
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;

    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    public double cost() {
        return coffee.cost() + 1.5;
    }

    public String description() {
        return coffee.description() + ", Milk";
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }

    public double cost() {
        return coffee.cost() + 0.5;
    }

    public String description() {
        return coffee.description() + ", Sugar";
    }
}