Java Design Patterns With What • Why • When • Full Java Programs • Client Usage

 

Java Design Patterns 

With What • Why • When • Full Java Programs • Client Usage


Introduction

Design Patterns are proven solutions to recurring software design problems.
They are not frameworks or libraries, but design ideas that help you write:

  • Maintainable code

  • Loosely coupled systems

  • Extensible architectures

In Java, design patterns are grouped into:

  1. Creational – How objects are created

  2. Structural – How objects are composed

  3. Behavioral – How objects interact


🟢 CREATIONAL PATTERNS


1️⃣ Singleton Pattern

What

Singleton ensures only one instance of a class exists in the entire application and provides a global access point to it.


Why

Some objects represent global shared state.
Creating multiple instances causes inconsistency.

Problems without Singleton:

  • Multiple config loaders with different values

  • Multiple loggers writing to the same file

  • Multiple cache managers


When

Use Singleton when:

  • Exactly one instance is required

  • Object is shared across the system

Real examples:

  • Configuration manager

  • Logger

  • Metrics registry


Full Java Program (with Client)

final class AppConfig { private static final AppConfig INSTANCE = new AppConfig(); private AppConfig() { // private constructor } public static AppConfig getInstance() { return INSTANCE; } public String getEnvironment() { return "PRODUCTION"; } } public class SingletonClient { public static void main(String[] args) { AppConfig c1 = AppConfig.getInstance(); AppConfig c2 = AppConfig.getInstance(); System.out.println(c1.getEnvironment()); System.out.println("Same instance? " + (c1 == c2)); } }

2️⃣ Factory Method Pattern

What

Factory Method creates objects without exposing the creation logic to the client.


Why

Direct object creation (new) tightly couples client code to concrete classes.

Problems without Factory:

  • Large if-else blocks

  • Client changes when implementation changes

  • Poor extensibility


When

Use Factory when:

  • Multiple implementations exist

  • Object type depends on input or config

Real examples:

  • Payment systems

  • Notification services

  • Parser creation


Full Java Program (with Client)

interface Payment { void pay(double amount); } class UpiPayment implements Payment { public void pay(double amount) { System.out.println("Paid " + amount + " using UPI"); } } class CardPayment implements Payment { public void pay(double amount) { System.out.println("Paid " + amount + " using Card"); } } class PaymentFactory { public static Payment create(String type) { if ("UPI".equalsIgnoreCase(type)) { return new UpiPayment(); } if ("CARD".equalsIgnoreCase(type)) { return new CardPayment(); } throw new IllegalArgumentException("Invalid payment type"); } } public class FactoryClient { public static void main(String[] args) { Payment payment = PaymentFactory.create("UPI"); payment.pay(1000); } }

3️⃣ Abstract Factory Pattern

What

Abstract Factory creates families of related objects that must work together.


Why

Creating related objects independently can cause incompatibility.

Example problem:

  • DarkButton + LightCheckbox ❌

  • AWSCompute + AzureStorage ❌


When

Use Abstract Factory when:

  • Objects must be used together

  • Multiple product families exist

Real examples:

  • UI themes

  • Cloud providers

  • OS-specific components


Full Java Program (with Client)

interface Button { void render(); } class DarkButton implements Button { public void render() { System.out.println("Rendering Dark Button"); } } class LightButton implements Button { public void render() { System.out.println("Rendering Light Button"); } } interface UIFactory { Button createButton(); } class DarkUIFactory implements UIFactory { public Button createButton() { return new DarkButton(); } } class LightUIFactory implements UIFactory { public Button createButton() { return new LightButton(); } } public class AbstractFactoryClient { public static void main(String[] args) { UIFactory factory = new DarkUIFactory(); Button button = factory.createButton(); button.render(); } }

4️⃣ Builder Pattern

What

Builder constructs complex objects step-by-step, separating construction from representation.


Why

Constructors with many parameters are:

  • Hard to read

  • Error-prone

  • Difficult to maintain


When

Use Builder when:

  • Object has many optional fields

  • Immutability is required

Real examples:

  • DTOs

  • API request objects

  • Configuration objects


Full Java Program (with Client)

class User { private final String name; private final int age; private final String city; private User(Builder builder) { this.name = builder.name; this.age = builder.age; this.city = builder.city; } static class Builder { private String name; private int age; private String city; Builder name(String name) { this.name = name; return this; } Builder age(int age) { this.age = age; return this; } Builder city(String city) { this.city = city; return this; } User build() { return new User(this); } } public String toString() { return name + ", " + age + ", " + city; } } public class BuilderClient { public static void main(String[] args) { User user = new User.Builder() .name("Vinod") .age(35) .city("Cupertino") .build(); System.out.println(user); } }

5️⃣ Prototype Pattern

What

Prototype creates new objects by cloning an existing object.


Why

Some objects are expensive to create.
Cloning is faster than rebuilding.


When

Use Prototype when:

  • Many similar objects are needed

  • Initialization is costly

Real examples:

  • Report templates

  • Game objects


Full Java Program (with Client)

class Report implements Cloneable { String title; Report(String title) { this.title = title; } public Report clone() { try { return (Report) super.clone(); } catch (Exception e) { throw new RuntimeException(e); } } } public class PrototypeClient { public static void main(String[] args) { Report template = new Report("Monthly Report"); Report jan = template.clone(); jan.title = "January Report"; System.out.println(jan.title); } }

🔵 STRUCTURAL PATTERNS


6️⃣ Adapter Pattern

What

Adapter converts one interface into another that the client expects.


Why

Legacy or third-party code cannot be modified, but your system expects a different interface.


When

Use Adapter when:

  • Integrating legacy systems

  • Using third-party SDKs

  • Interfaces don’t match


Full Java Program (with Client)

class LegacyLogger { void writeLog(String msg) { System.out.println("Legacy: " + msg); } } interface Logger { void log(String msg); } class LoggerAdapter implements Logger { private final LegacyLogger legacyLogger; LoggerAdapter(LegacyLogger legacyLogger) { this.legacyLogger = legacyLogger; } public void log(String msg) { legacyLogger.writeLog(msg); } } public class AdapterClient { public static void main(String[] args) { Logger logger = new LoggerAdapter(new LegacyLogger()); logger.log("Application started"); } }

7️⃣ Decorator Pattern

What

Decorator adds new behavior dynamically without modifying the original class.


Why

Subclassing leads to class explosion.
Decorator uses composition instead.


When

Use Decorator when:

  • Behavior is optional

  • Multiple features must be combined

Real examples:

  • Logging

  • Security

  • Metrics


Full Java Program (with Client)

interface Service { String execute(); } class CoreService implements Service { public String execute() { return "Core Service"; } } class LoggingDecorator implements Service { private final Service service; LoggingDecorator(Service service) { this.service = service; } public String execute() { System.out.println("Before execution"); String result = service.execute(); System.out.println("After execution"); return result; } } public class DecoratorClient { public static void main(String[] args) { Service service = new LoggingDecorator(new CoreService()); service.execute(); } }

8️⃣ Facade Pattern

What

Facade provides a simple interface to a complex subsystem.


Why

Subsystems are difficult to use directly.


When

Use Facade when:

  • System has many internal classes

  • Client needs a simple API


Full Java Program (with Client)

class Video { void play() { System.out.println("Playing video"); } } class Audio { void play() { System.out.println("Playing audio"); } } class MediaFacade { private final Video video = new Video(); private final Audio audio = new Audio(); void play() { video.play(); audio.play(); } } public class FacadeClient { public static void main(String[] args) { new MediaFacade().play(); } }

9️⃣ Proxy Pattern

What

Proxy controls access to another object.


Why

Direct access may be expensive or insecure.


When

Use Proxy when:

  • Lazy loading is needed

  • Access control is required


Full Java Program (with Client)

interface Image { void display(); } class RealImage implements Image { RealImage() { System.out.println("Loading image from disk..."); } public void display() { System.out.println("Displaying image"); } } class ImageProxy implements Image { private RealImage realImage; public void display() { if (realImage == null) { realImage = new RealImage(); } realImage.display(); } } public class ProxyClient { public static void main(String[] args) { Image image = new ImageProxy(); image.display(); } }
 
 

No comments:

Post a Comment

Java Behavioral Desigh Patterns

  🟣 BEHAVIORAL PATTERNS (How objects interact, communicate, and change behavior) Behavioral patterns focus on: Communication between ob...

Featured Posts