DEV Community

Cover image for Java Design Patterns in Real Projects
Madhan Kumar
Madhan Kumar

Posted on • Edited on

Java Design Patterns in Real Projects

Mastering Design Patterns to Solve Real Development Challenges:

Introduction:

Design patterns are not just something developers learn for exams or interviews. They are practical solutions to recurring problems in software development. These patterns help create clean, maintainable, and reusable code.

Whether you are building enterprise-level applications or small microservices, the right design pattern can simplify your codebase and improve long-term development efficiency.

This article walks through five widely-used design patterns in Java, each accompanied by a real-world use case.

Singleton Pattern:

Purpose: Ensure that a class has only one instance while providing a global point of access to it.
Real-world scenario: You are building a logging service. You do not want multiple instances of the logger writing conflicting log entries. With the Singleton pattern, you ensure there is only one logger instance throughout the application.

public class Logger {
    private static Logger instance;

    private Logger() {}

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

    public void log(String message) {
        System.out.println(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Logging
  • Configuration
  • Caching
  • Shared resources

Builder Pattern:

Purpose: Simplify the creation of complex objects by building them step by step.

Real-world scenario: Suppose you have a User object with several optional fields such as email, phone, and address. Instead of using multiple constructors or exposing setters, the Builder pattern gives you a clean and readable approach to construct objects.

public class User {
    private final String name;
    private final String email;
    private final String phone;

    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.phone = builder.phone;
    }

    public static class Builder {
        private String name;
        private String email;
        private String phone;

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

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

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

        public User build() {
            return new User(this);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Creating objects with many optional parameters
  • Building immutable objects
  • Improving code readability

Strategy Pattern:

Purpose: Define a group of algorithms, encapsulate each one, and make them interchangeable without changing the code that uses them.

Real-world scenario: In a payment gateway, you may support credit card, UPI, and wallet payments. Instead of hard-coding logic, the Strategy pattern lets you plug in new payment methods easily without modifying existing logic.

public interface PaymentStrategy {
    void pay(double amount);
}

public class CreditCardPayment implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("Paid " + amount + " using credit card");
    }
}

public class UpiPayment implements PaymentStrategy {
    public void pay(double amount) {
        System.out.println("Paid " + amount + " through UPI");
    }
}
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Dynamic behavior switching
  • Payment systems
  • Sorting or filtering logic
  • Validation rules

Factory Method Pattern:

Purpose: Provide an interface for creating objects while allowing subclasses or logic to determine which class to instantiate.

Real-world scenario: You are building a notification system that supports email, SMS, and push notifications. The Factory Method pattern allows you to return the correct object based on input or configuration.

public interface Notification {
    void notifyUser();
}

public class EmailNotification implements Notification {
    public void notifyUser() {
        System.out.println("Sending Email Notification");
    }
}

public class NotificationFactory {
    public static Notification createNotification(String type) {
        if ("EMAIL".equalsIgnoreCase(type)) {
            return new EmailNotification();
        }
        // Add more notification types here
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Object creation based on configuration or user input
  • Plugin or extension systems
  • Frameworks that create components dynamically

Observer Pattern:

Purpose: Define a one-to-many relationship so that when one object changes its state, all its dependents are notified automatically.

Real-world scenario: You are creating a stock trading platform. Whenever the price of a stock changes, all registered observers (such as dashboard widgets or user alerts) must receive an update.

public interface Observer {
    void update(String message);
}

public class StockObserver implements Observer {
    private String name;

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

    public void update(String message) {
        System.out.println(name + " received: " + message);
    }
}
Enter fullscreen mode Exit fullscreen mode

When to use:

  • Event-driven systems
  • Chat applications
  • Data binding in user interfaces
  • Publish and subscribe mechanisms

Closing Thoughts:

  • Design patterns are not magic formulas, but they offer proven ways to solve common software design problems. Learning when and how to use them can make your codebase more organized, more testable, and easier to maintain.
  • You do not need to force them into every situation, but over time, you will begin to recognize where a pattern fits naturally. These five are just the beginning. The deeper you go, the more you will appreciate how design patterns bring clarity and elegance to your software architecture.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.