Design Patterns

 

What are Design Patterns in Java?


In Java, a design pattern is a standard, reusable solution to a recurring software design problem.

  • They are not code, but templates or best practices that you can adapt to your project.
  • Design patterns help make code more flexible, reusable, and easier to maintain.
  • They solve problems related to object creationobject composition, and communication between objects.

Why Use Them?

  • Avoid reinventing the wheel
  • Improve code readability
  • Provide a shared vocabulary among developers
  • Make systems more robust and adaptable to change

What Is GoF (Gang of Four) ?
  • Nickname for the four authors of the landmark 1994 book:
    “Design Patterns: Elements of Reusable Object-Oriented Software”
    • Erich Gamma
    • Richard Helm
    • Ralph Johnson
    • John Vlissides
  • This book introduced the term “design patterns” to the programming world in a formal way and documented 23 classic patterns.

Why GoF is Important in Java

  • Java, being an object-oriented language, naturally benefits from the GoF patterns.
  • Most Java frameworks (Spring, Hibernate, etc.) use multiple GoF patterns internally.
  • The GoF book became the foundation for modern software design principles.

Categories of Design Patterns 


The GoF patterns are grouped into three main categories 

Category

Purpose

Creational    

Handle object creation in a way that hides complexity and makes the system independent of object creation details.

Structural

Focus on class and object composition — building larger structures from smaller parts.

Behavioral

Focus on communication between objects and responsibility distribution.



List of All Design Patterns

a) GoF Patterns (23 in total) 


Creational (5)

1. Singleton – Ensures only one instance exists and provides global access.

2. Factory Method – Creates objects without specifying the exact class.

3. Abstract Factory – Creates families of related objects without specifying classes.

4. Builder – Constructs complex objects step-by-step.

5. Prototype – Creates new objects by cloning an existing object.


Structural (7)
6. Adapter – Allows incompatible interfaces to work together.
7. Bridge – Separates abstraction from implementation.
8. Composite – Treats individual objects and object groups uniformly.
9. Decorator – Adds new behavior to objects dynamically.
10. Facade – Provides a simplified interface to a complex system.
11. Flyweight – Minimizes memory by sharing common object state.
12. Proxy – Provides a placeholder or surrogate for another object.


Behavioral (11)
13. Chain of Responsibility – Passes request along a chain until handled.
14. Command – Encapsulates a request as an object.
15. Interpreter – Defines grammar and interprets sentences.
16. Iterator – Sequentially accesses elements without exposing structure.
17. Mediator – Centralizes complex communications between objects.
18. Memento – Captures and restores an object’s state.
19. Observer – Notifies multiple objects when a state changes.
20. State – Alters behavior when internal state changes.
21. Strategy – Encapsulates interchangeable algorithms.
22. Template Method – Defines algorithm skeleton, deferring steps to subclasses.
23. Visitor – Separates algorithms from object structures.


b) Non-GoF Patterns 


Enterprise / Java EE Patterns
24. DAO – Separates persistence logic from business logic.
25. DTO – Transfers data between layers without exposing domain models.
26. Service Locator – Central registry for service lookups.
27. Business Delegate – Simplifies client interaction with business services.
28. Repository – Manages collection-like access to domain objects.
29. Factory Bean – Spring-specific configurable object creator.
30. Dependency Injection – Injects object dependencies at runtime.
31. Lazy Initialization – Defers object creation until needed.
32. Front Controller – Central request handler in web apps.
33. Intercepting Filter – Pre/post-processing of web requests.
34. Composite Entity – Represents a graph of objects in EJB.
35. Value Object – Immutable object for holding data.
36. Session Facade – Simplifies EJB interactions.


Concurrency Patterns
37. Producer–Consumer – Separates producers of data from consumers.
38. Thread Pool – Manages a pool of reusable threads.
39. Read–Write Lock – Allows concurrent reads but exclusive writes.
40. Future – Represents a result of an async computation.
41. Scheduler – Manages timed or recurring tasks.
42. Reactor – Handles service requests concurrently using event demultiplexing.
43. Guarded Suspension – Waits for a condition before execution.
44. Balking – Avoids executing an action if the system is in the wrong state.


Architectural Patterns
45. MVC – Separates application into Model, View, Controller.
46. MVVM – Separates Model, View, ViewModel.
47. MVP – Separates Model, View, Presenter.
48. Layered Architecture – Organizes system into layers.
49. Hexagonal Architecture – Decouples core logic from external systems.
50. Microkernel Architecture – Minimal core with plug-in modules.
51. Microservices – Distributed services with independent deployment.
52. Event-Driven – Uses events to trigger actions asynchronously.
53. CQRS – Separates command and query responsibilities.
54. Pipe & Filter – Chains processing steps via data streams.


Integration Patterns
55. API Gateway – Single entry point for APIs.
56. Circuit Breaker – Prevents cascading failures in distributed systems.
57. Saga – Manages distributed transactions in microservices.
58. Publish–Subscribe – One-to-many asynchronous messaging.
59. Message Broker – Mediates message passing between systems.
60. Service Registry & Discovery – Keeps track of service locations.
61. Strangler Fig – Incremental migration from legacy to new system.




Examples for All 23 GoF Patterns



Creational GoF patterns (5)


1. Singleton Pattern

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

Example Problem:
A logging service, configuration manager, or database connection pool should have only one instance to avoid inconsistent states.

Java Example:

public class Logger {

    private static Logger instance;


    private Logger() {} // private constructor


    public static Logger getInstance() {

        if (instance == null) {

            instance = new Logger();

        }

        return instance;

    }


    public void log(String message) {

        System.out.println("[LOG] " + message);

    }

}


class Main {

    public static void main(String[] args) {

        Logger logger = Logger.getInstance();

        logger.log("Application started");

    }

}

Notes:

  • Can be lazy-loaded (as above) or eager-loaded.
  • In multithreaded environments, use synchronized or Double-Checked Locking.

Real-Time Use Cases:

  • Spring Beans (default scope is singleton)
  • Logging frameworks (Log4j, SLF4J)
  • Database connection pools (HikariDataSource)
  • Caching services (Ehcache, RedisTemplate)


2. Factory Method Pattern

Intent:
Define an interface for creating an object, but let subclasses decide which class to instantiate.

Example Problem:
A payment gateway should support different payment types (Credit Card, UPI, PayPal) without the calling code knowing the exact class.

Java Example:

interface Payment {

    void pay(double amount);

}


class UpiPayment implements Payment {

    public void pay(double amount) { System.out.println("Paid via UPI: " + amount); }

}


abstract class PaymentFactory {

    abstract Payment createPayment();

}


class UpiPaymentFactory extends PaymentFactory {

    public Payment createPayment() { return new UpiPayment(); }

}


class Main {

    public static void main(String[] args) {

        PaymentFactory factory = new UpiPaymentFactory();

        Payment payment = factory.createPayment();

        payment.pay(500);

    }

}

Notes:

  • Promotes loose coupling and easy extension.
  • Only knows the interface, not the implementation.

Real-Time Use Cases:

  • Calendar.getInstance() in Java.
  • Spring’s BeanFactory for bean creation.
  • Payment processing systems supporting multiple providers.


3. Abstract Factory Pattern

Intent:
Provide an interface to create families of related objects without specifying concrete classes.

Example Problem:
A UI framework should generate Windows or Mac components depending on the OS.

Java Example:

interface Button { void paint(); }

interface Checkbox { void render(); }


class WinButton implements Button {

    public void paint() { System.out.println("Windows Button"); }

}

class WinCheckbox implements Checkbox {

    public void render() { System.out.println("Windows Checkbox"); }

}


interface GUIFactory {

    Button createButton();

    Checkbox createCheckbox();

}


class WinFactory implements GUIFactory {

    public Button createButton() { return new WinButton(); }

    public Checkbox createCheckbox() { return new WinCheckbox(); }

}


class Main {

    public static void main(String[] args) {

        GUIFactory factory = new WinFactory();

        factory.createButton().paint();

        factory.createCheckbox().render();

    }

}

Notes:

  • Ensures that related components are compatible.
  • Good for product families with variations.

Real-Time Use Cases:

  • JDBC drivers (DriverManager.getConnection() returns DB-specific objects).
  • Spring @Profile to load environment-specific beans.
  • Cross-platform UI toolkits.


4. Builder Pattern

Intent:
Separate the construction of a complex object from its representation so the same process can create different configurations.

Example Problem:
Building a RestTemplate with optional configurations like timeout and interceptors.

Java Example:

class RestClient {

    String baseUrl;

    int timeout;


    static class Builder {

        String baseUrl;

        int timeout;


        Builder baseUrl(String url) { this.baseUrl = url; return this; }

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

        RestClient build() { return new RestClient(this); }

    }


    private RestClient(Builder b) {

        this.baseUrl = b.baseUrl;

        this.timeout = b.timeout;

    }

}


class Main {

    public static void main(String[] args) {

        RestClient client = new RestClient.Builder()

                .baseUrl("https://api.example.com")

                .timeout(5000)

                .build();

    }

}

Notes:

  • Avoids telescoping constructor problem.
  • Great for immutable objects.

Real-Time Use Cases:

  • StringBuilder in Java.
  • Lombok’s @Builder for DTOs.
  • Spring’s UriComponentsBuilder.


5. Prototype Pattern

Intent:
Create new objects by copying an existing object instead of creating from scratch.

Example Problem:
Cloning a configuration object for testing with small modifications.

Java Example:

class Report implements Cloneable {

    String title;


    Report(String title) { this.title = title; }


    public Report clone() {

        try { return (Report) super.clone(); }

        catch (CloneNotSupportedException e) { return null; }

    }

}


class Main {

    public static void main(String[] args) {

        Report r1 = new Report("Sales Q1");

        Report r2 = r1.clone();

        System.out.println(r2.title);

    }

}

Notes:

  • Useful when object creation is expensive.
  • Watch out for deep vs shallow copy.

Real-Time Use Cases:


  • Spring @Scope("prototype") beans.
  • Document/template duplication.
  • Game object cloning.



Structural GoF patterns (7) 


1. Adapter Pattern

Intent:
Convert the interface of a class into another interface clients expect.

Example Problem:
You have a legacy payment service that processes in rupees, but your new system expects amounts in dollars.

Java Example:

interface PaymentGateway {

    void payUSD(double amount);

}


class RupeePayment {

    void payINR(double amount) {

        System.out.println("Paid ₹" + amount);

    }

}


class PaymentAdapter implements PaymentGateway {

    private RupeePayment rupeePayment = new RupeePayment();


    public void payUSD(double amount) {

        rupeePayment.payINR(amount * 83); // Convert USD → INR

    }

}


class Main {

    public static void main(String[] args) {

        PaymentGateway gateway = new PaymentAdapter();

        gateway.payUSD(10);

    }

}

Notes:

  • Acts as a bridge between incompatible interfaces.
  • Doesn’t change existing classes.

Real-Time Use Cases:

  • InputStreamReader / OutputStreamWriter in Java I/O.
  • Spring’s AdapterPattern in HandlerAdapter.
  • Integrating old APIs into new systems.


2. Bridge Pattern

Intent:
Decouple an abstraction from its implementation so both can vary independently.

Example Problem:
A remote control should work for multiple TV brands without hardcoding brand-specific logic in the remote.

Java Example:

interface Device { void turnOn(); }

class SonyTV implements Device { public void turnOn() { System.out.println("Sony TV ON"); } }

class LGTV implements Device { public void turnOn() { System.out.println("LG TV ON"); } }


abstract class Remote {

    protected Device device;

    Remote(Device device) { this.device = device; }

    abstract void pressOn();

}


class BasicRemote extends Remote {

    BasicRemote(Device device) { super(device); }

    void pressOn() { device.turnOn(); }

}


class Main {

    public static void main(String[] args) {

        Remote remote = new BasicRemote(new SonyTV());

        remote.pressOn();

    }

}

Notes:

  • Splits abstraction (Remote) from implementation (Device).
  • Easier to extend both sides.

Real-Time Use Cases:

  • JDBC API (abstraction) with multiple drivers (implementations).
  • Spring JdbcTemplate with different databases.
  • Graphics libraries supporting multiple rendering engines.


3. Composite Pattern

Intent:
Compose objects into tree structures to represent part-whole hierarchies.

Example Problem:
Represent a folder structure where folders can contain files and subfolders.

Java Example:

import java.util.*;


interface FileComponent {

    void showDetails();

}


class FileLeaf implements FileComponent {

    String name;

    FileLeaf(String name) { this.name = name; }

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

}


class Folder implements FileComponent {

    String name;

    List<FileComponent> children = new ArrayList<>();

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

    public void add(FileComponent c) { children.add(c); }

    public void showDetails() {

        System.out.println("Folder: " + name);

        for (FileComponent c : children) c.showDetails();

    }

}


class Main {

    public static void main(String[] args) {

        Folder root = new Folder("Root");

        root.add(new FileLeaf("file1.txt"));

        Folder sub = new Folder("Sub");

        sub.add(new FileLeaf("file2.txt"));

        root.add(sub);

        root.showDetails();

    }

}

Notes:

  • Treats individual and composite objects uniformly.
  • Ideal for hierarchical data.

Real-Time Use Cases:

  • DOM tree in HTML/XML parsing.
  • Spring Security role hierarchy.
  • File explorers.

4. Decorator Pattern

Intent:
Attach additional responsibilities to an object dynamically.

Example Problem:
Add compression and encryption features to a file output stream without modifying the original class.

Java Example:

interface DataSource { void write(String data); }

class FileDataSource implements DataSource {

    public void write(String data) { System.out.println("Writing: " + data); }

}


class DataSourceDecorator implements DataSource {

    protected DataSource wrappee;

    DataSourceDecorator(DataSource source) { this.wrappee = source; }

    public void write(String data) { wrappee.write(data); }

}


class EncryptionDecorator extends DataSourceDecorator {

    EncryptionDecorator(DataSource source) { super(source); }

    public void write(String data) { super.write("[Encrypted]" + data); }

}


class Main {

    public static void main(String[] args) {

        DataSource source = new EncryptionDecorator(new FileDataSource());

        source.write("Hello");

    }

}

Notes:

  • Enhances objects at runtime without altering base code.
  • More flexible than subclassing.

Real-Time Use Cases:

  • Java I/O Streams (BufferedInputStream, DataOutputStream).
  • Spring’s BeanPostProcessor.
  • Servlet filters.


5. Facade Pattern

Intent:
Provide a unified interface to a set of interfaces in a subsystem.

Example Problem:
Simplify interaction with multiple complex services like payment, notification, and shipping.

Java Example:

class PaymentService { void process() { System.out.println("Payment processed"); } }

class NotificationService { void send() { System.out.println("Notification sent"); } }

class ShippingService { void ship() { System.out.println("Item shipped"); } }


class OrderFacade {

    private PaymentService payment = new PaymentService();

    private NotificationService notification = new NotificationService();

    private ShippingService shipping = new ShippingService();


    void placeOrder() {

        payment.process();

        notification.send();

        shipping.ship();

    }

}


class Main {

    public static void main(String[] args) {

        new OrderFacade().placeOrder();

    }

}

Notes:

  • Hides subsystem complexity.
  • Reduces dependency on internal details.

Real-Time Use Cases:

  • java.util.logging.Logger methods.
  • Spring’s JdbcTemplate hiding JDBC boilerplate.
  • REST client wrappers.


6. Flyweight Pattern

Intent:
Minimize memory by sharing as much data as possible between similar objects.

Example Problem:
Rendering a large forest where each tree shares texture and color data.

Java Example:

class TreeType {

    String name, color;

    TreeType(String name, String color) { this.name = name; this.color = color; }

}


class Tree {

    int x, y;

    TreeType type;

    Tree(int x, int y, TreeType type) { this.x = x; this.y = y; this.type = type; }

}


class TreeFactory {

    static TreeType oakType = new TreeType("Oak", "Green");

    static Tree getOak(int x, int y) { return new Tree(x, y, oakType); }

}


class Main {

    public static void main(String[] args) {

        Tree t1 = TreeFactory.getOak(1, 1);

        Tree t2 = TreeFactory.getOak(2, 2);

        System.out.println(t1.type == t2.type); // true

    }

}

Notes:

  • Great for objects with shared state.
  • Reduces RAM usage.

Real-Time Use Cases:

  • String pool in Java.
  • Character objects in text editors.
  • Game asset sharing.


7. Proxy Pattern

Intent:
Provide a placeholder or surrogate to control access to another object.

Example Problem:
Control access to an expensive object (like a video loader) until it’s actually needed.

Java Example:

interface Video { void play(); }

class RealVideo implements Video {

    String filename;

    RealVideo(String filename) { this.filename = filename; load(); }

    void load() { System.out.println("Loading " + filename); }

    public void play() { System.out.println("Playing " + filename); }

}


class ProxyVideo implements Video {

    RealVideo realVideo;

    String filename;

    ProxyVideo(String filename) { this.filename = filename; }

    public void play() {

        if (realVideo == null) realVideo = new RealVideo(filename);

        realVideo.play();

    }

}


class Main {

    public static void main(String[] args) {

        Video video = new ProxyVideo("movie.mp4");

        video.play(); // Loads only when played

    }

}

Notes:

  • Controls creation or access.
  • Can implement lazy loading, access control, logging.

Real-Time Use Cases:

  • Hibernate lazy loading.
  • Spring AOP proxies.
  • Security proxies for authentication.


Behavioral GoF patterns (11)


1. Chain of Responsibility Pattern

Intent:
Pass a request along a chain of handlers until one of them processes it.

Example Problem:
You want different levels of support staff (Level 1, Level 2, Manager) to handle customer complaints.

Java Example:

abstract class SupportHandler {

    protected SupportHandler next;

    public void setNext(SupportHandler next) { this.next = next; }

    public abstract void handleRequest(String issue);

}


class Level1Support extends SupportHandler {

    public void handleRequest(String issue) {

        if (issue.equals("basic")) System.out.println("Handled by Level 1");

        else if (next != null) next.handleRequest(issue);

    }

}


class Level2Support extends SupportHandler {

    public void handleRequest(String issue) {

        if (issue.equals("advanced")) System.out.println("Handled by Level 2");

        else if (next != null) next.handleRequest(issue);

    }

}


class ManagerSupport extends SupportHandler {

    public void handleRequest(String issue) {

        System.out.println("Handled by Manager");

    }

}


class Main {

    public static void main(String[] args) {

        SupportHandler l1 = new Level1Support();

        SupportHandler l2 = new Level2Support();

        SupportHandler mgr = new ManagerSupport();

        l1.setNext(l2);

        l2.setNext(mgr);


        l1.handleRequest("advanced");

    }

}

Notes:

  • Promotes loose coupling between sender and receivers.
  • Order of handlers matters.

Real-Time Use Cases:

  • Servlet filter chains.
  • Exception handling chains.
  • Spring Security filter chain.


2. Command Pattern

Intent:
Encapsulate a request as an object, allowing parameterization and queuing of requests.

Example Problem:
Implement an undo/redo system for a text editor.

Java Example:

interface Command { void execute(); }

class TextEditor {

    void write(String text) { System.out.println("Writing: " + text); }

}

class WriteCommand implements Command {

    private TextEditor editor; private String text;

    WriteCommand(TextEditor editor, String text) { this.editor = editor; this.text = text; }

    public void execute() { editor.write(text); }

}


class Main {

    public static void main(String[] args) {

        TextEditor editor = new TextEditor();

        Command cmd = new WriteCommand(editor, "Hello");

        cmd.execute();

    }

}

Notes:

  • Decouples sender and receiver.
  • Supports undo/redo, logging, queuing.

Real-Time Use Cases:

  • Runnable in Java threading.
  • GUI button actions.
  • Job scheduling.


3. Interpreter Pattern

Intent:
Define a grammar and interpret sentences in that grammar.

Example Problem:
Build a simple language to check if a word contains a specific letter.

Java Example:

interface Expression { boolean interpret(String context); }

class ContainsExpression implements Expression {

    private String data;

    ContainsExpression(String data) { this.data = data; }

    public boolean interpret(String context) { return context.contains(data); }

}


class Main {

    public static void main(String[] args) {

        Expression isJava = new ContainsExpression("Java");

        System.out.println(isJava.interpret("I love Java")); // true

    }

}

Notes:

  • Works best for small grammars.
  • Can be slow for complex parsing.

Real-Time Use Cases:

  • SQL query parsing.
  • Regular expression engines.
  • Rule engines.


4. Iterator Pattern

Intent:
Provide a way to access elements of a collection without exposing its representation.

Example Problem:
Create a custom iterator for a collection of names.

Java Example:

interface Iterator<T> { boolean hasNext(); T next(); }

interface Container<T> { Iterator<T> getIterator(); }


class NameRepository implements Container<String> {

    private String[] names = {"A", "B", "C"};

    public Iterator<String> getIterator() {

        return new Iterator<String>() {

            int index = 0;

            public boolean hasNext() { return index < names.length; }

            public String next() { return names[index++]; }

        };

    }

}


class Main {

    public static void main(String[] args) {

        NameRepository repo = new NameRepository();

        Iterator<String> it = repo.getIterator();

        while (it.hasNext()) System.out.println(it.next());

    }

}

Notes:

  • Keeps collection encapsulated.
  • Iteration logic separated.

Real-Time Use Cases:

  • Java Iterator interface.
  • ResultSet iteration in JDBC.
  • For-each loops.


5. Mediator Pattern

Intent:
Define an object that encapsulates how a set of objects interact.

Example Problem:
Implement a chat room where users talk to each other via a mediator.

Java Example:

import java.util.*;


class ChatMediator {

    private List<User> users = new ArrayList<>();

    public void addUser(User user) { users.add(user); }

    public void sendMessage(String msg, User sender) {

        for (User u : users) if (u != sender) u.receive(msg);

    }

}


class User {

    private String name;

    private ChatMediator mediator;

    User(String name, ChatMediator mediator) { this.name = name; this.mediator = mediator; }

    public void send(String msg) { mediator.sendMessage(msg, this); }

    public void receive(String msg) { System.out.println(name + " received: " + msg); }

}


class Main {

    public static void main(String[] args) {

        ChatMediator mediator = new ChatMediator();

        User u1 = new User("Alice", mediator);

        User u2 = new User("Bob", mediator);

        mediator.addUser(u1); mediator.addUser(u2);


        u1.send("Hello!");

    }

}

Notes:

  • Reduces coupling between components.
  • Centralizes control.

Real-Time Use Cases:

  • Chat room applications.
  • Air traffic control systems.
  • GUI frameworks event handling.


6. Memento Pattern

Intent:
Capture and restore an object’s state without exposing its internals.

Example Problem:
Implement undo functionality for a text editor.

Java Example:

class Editor {

    private String content;

    public void setContent(String content) { this.content = content; }

    public String getContent() { return content; }

    public Memento save() { return new Memento(content); }

    public void restore(Memento m) { content = m.getState(); }

}


class Memento {

    private final String state;

    Memento(String state) { this.state = state; }

    public String getState() { return state; }

}


class Main {

    public static void main(String[] args) {

        Editor editor = new Editor();

        editor.setContent("A");

        Memento saved = editor.save();

        editor.setContent("B");

        editor.restore(saved);

        System.out.println(editor.getContent()); // A

    }

}

Notes:

  • Keeps encapsulation intact.
  • Caretaker manages mementos.

Real-Time Use Cases:

  • Undo in editors.
  • Game save states.
  • Transaction rollback.


7. Observer Pattern

Intent:
Define a dependency so that when one object changes state, all dependents are notified.

Example Problem:
A weather station updates multiple displays when temperature changes.

Java Example:

import java.util.*;


interface Observer { void update(int temp); }

class WeatherStation {

    private List<Observer> observers = new ArrayList<>();

    private int temperature;

    public void addObserver(Observer o) { observers.add(o); }

    public void setTemperature(int temp) {

        this.temperature = temp;

        for (Observer o : observers) o.update(temp);

    }

}


class Display implements Observer {

    public void update(int temp) { System.out.println("Temp: " + temp); }

}


class Main {

    public static void main(String[] args) {

        WeatherStation ws = new WeatherStation();

        ws.addObserver(new Display());

        ws.setTemperature(30);

    }

}

Notes:

  • Loosely couples subject and observers.
  • Pull or push model possible.

Real-Time Use Cases:

  • Java Observable.
  • Event listeners in Swing.
  • Stock market feeds.


8. State Pattern

Intent:
Allow an object to change its behavior when its internal state changes.

Example Problem:
A traffic light changes behavior based on current color.

Java Example:

interface State { void handle(); }

class RedState implements State { public void handle() { System.out.println("Stop!"); } }

class GreenState implements State { public void handle() { System.out.println("Go!"); } }


class TrafficLight {

    private State state;

    public void setState(State state) { this.state = state; }

    public void request() { state.handle(); }

}


class Main {

    public static void main(String[] args) {

        TrafficLight light = new TrafficLight();

        light.setState(new RedState()); light.request();

        light.setState(new GreenState()); light.request();

    }

}

Notes:

  • Replaces big if-else with polymorphism.
  • Easier state transitions.

Real-Time Use Cases:

  • Vending machines.
  • Game AI.
  • Workflow engines.


9. Strategy Pattern

Intent:
Define a family of algorithms, encapsulate each one, and make them interchangeable.

Example Problem:
Implement multiple payment methods in an e-commerce app.

Java Example:

interface PaymentStrategy { void pay(int amount); }

class CreditCardPayment implements PaymentStrategy {

    public void pay(int amount) { System.out.println("Paid $" + amount + " via Credit Card"); }

}

class PayPalPayment implements PaymentStrategy {

    public void pay(int amount) { System.out.println("Paid $" + amount + " via PayPal"); }

}


class PaymentContext {

    private PaymentStrategy strategy;

    public PaymentContext(PaymentStrategy strategy) { this.strategy = strategy; }

    public void pay(int amount) { strategy.pay(amount); }

}


class Main {

    public static void main(String[] args) {

        PaymentContext ctx = new PaymentContext(new CreditCardPayment());

        ctx.pay(100);

        ctx = new PaymentContext(new PayPalPayment());

        ctx.pay(200);

    }

}

Notes:

  • Avoids hardcoding algorithm logic.
  • Promotes Open/Closed Principle.

Real-Time Use Cases:

  • Sorting with different comparators.
  • Payment gateways.
  • Compression algorithms.


10. Template Method Pattern

Intent:
Define the skeleton of an algorithm, deferring some steps to subclasses.

Example Problem:
Build a generic data processing workflow where specific steps vary.

Java Example:

abstract class DataProcessor {

    public final void process() {

        readData();

        processData();

        saveData();

    }

    abstract void readData();

    abstract void processData();

    void saveData() { System.out.println("Saving processed data"); }

}


class CSVProcessor extends DataProcessor {

    void readData() { System.out.println("Reading CSV"); }

    void processData() { System.out.println("Processing CSV"); }

}


class Main {

    public static void main(String[] args) {

        DataProcessor p = new CSVProcessor();

        p.process();

    }

}

Notes:

  • Enforces overall structure.
  • Some steps fixed, some customizable.

Real-Time Use Cases:

  • HttpServlet doGet/doPost in Java.
  • Build systems.
  • Game AI routines.


11. Visitor Pattern

Intent:
Separate an algorithm from the object structure it operates on.

Example Problem:
Perform operations (like tax calculation, price display) on different product types without modifying them.

Java Example:

interface Visitor { void visit(Book book); void visit(Fruit fruit); }

interface Item { void accept(Visitor visitor); }


class Book implements Item {

    public void accept(Visitor visitor) { visitor.visit(this); }

}

class Fruit implements Item {

    public void accept(Visitor visitor) { visitor.visit(this); }

}


class PriceVisitor implements Visitor {

    public void visit(Book book) { System.out.println("Book price calculated"); }

    public void visit(Fruit fruit) { System.out.println("Fruit price calculated"); }

}


class Main {

    public static void main(String[] args) {

        Item[] items = {new Book(), new Fruit()};

        Visitor visitor = new PriceVisitor();

        for (Item i : items) i.accept(visitor);

    }

}

Notes:

  • Good when operations change often but object structure remains stable.
  • Can be verbose.

Real-Time Use Cases:

  • AST processing in compilers.
  • Document processing.
  • UI component rendering.