Design Patterns in Java

Design Patterns are proven solutions to common software design problems. They provide reusable templates for solving recurring design challenges in object-oriented programming.

1. Creational Patterns

1.1 Singleton Pattern

Ensures a class has only one instance and provides a global point of access.

public class Singleton {
// 1. Eager Initialization
public static class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
// Private constructor to prevent instantiation
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
public void showMessage() {
System.out.println("Eager Singleton instance: " + this);
}
}
// 2. Lazy Initialization with Double-Checked Locking
public static class LazySingleton {
private static volatile LazySingleton instance;
private LazySingleton() {
// Prevent reflection attacks
if (instance != null) {
throw new IllegalStateException("Instance already created");
}
}
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
public void showMessage() {
System.out.println("Lazy Singleton instance: " + this);
}
}
// 3. Bill Pugh Singleton (Most Recommended)
public static class BillPughSingleton {
private BillPughSingleton() {
// Private constructor
}
private static class SingletonHelper {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHelper.INSTANCE;
}
public void showMessage() {
System.out.println("Bill Pugh Singleton instance: " + this);
}
}
// 4. Enum Singleton (Thread-safe and serialization-safe)
public enum EnumSingleton {
INSTANCE;
public void showMessage() {
System.out.println("Enum Singleton instance: " + this);
}
}
}
// Usage
class SingletonDemo {
public static void main(String[] args) {
// Eager Singleton
Singleton.EagerSingleton eager1 = Singleton.EagerSingleton.getInstance();
Singleton.EagerSingleton eager2 = Singleton.EagerSingleton.getInstance();
eager1.showMessage();
System.out.println("Same instance: " + (eager1 == eager2));
// Lazy Singleton
Singleton.LazySingleton lazy1 = Singleton.LazySingleton.getInstance();
Singleton.LazySingleton lazy2 = Singleton.LazySingleton.getInstance();
lazy1.showMessage();
System.out.println("Same instance: " + (lazy1 == lazy2));
// Enum Singleton
Singleton.EnumSingleton enum1 = Singleton.EnumSingleton.INSTANCE;
Singleton.EnumSingleton enum2 = Singleton.EnumSingleton.INSTANCE;
enum1.showMessage();
System.out.println("Same instance: " + (enum1 == enum2));
}
}

1.2 Factory Method Pattern

Creates objects without specifying the exact class of object that will be created.

// Product Interface
interface Notification {
void send(String message);
}
// 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);
}
}
// Creator Abstract Class
abstract class NotificationFactory {
// Factory method
public abstract Notification createNotification();
public void sendNotification(String message) {
Notification notification = createNotification();
notification.send(message);
}
}
// Concrete Creators
class EmailNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new EmailNotification();
}
}
class SMSNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new SMSNotification();
}
}
class PushNotificationFactory extends NotificationFactory {
@Override
public Notification createNotification() {
return new PushNotification();
}
}
// Usage
class FactoryMethodDemo {
public static void main(String[] args) {
NotificationFactory emailFactory = new EmailNotificationFactory();
emailFactory.sendNotification("Hello via Email!");
NotificationFactory smsFactory = new SMSNotificationFactory();
smsFactory.sendNotification("Hello via SMS!");
NotificationFactory pushFactory = new PushNotificationFactory();
pushFactory.sendNotification("Hello via Push!");
}
}

1.3 Abstract Factory Pattern

Provides an interface for creating families of related objects without specifying their concrete classes.

// Abstract Products
interface Button {
void render();
void onClick();
}
interface Checkbox {
void render();
void onCheck();
}
// Concrete Products for Windows
class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Rendering Windows-style button");
}
@Override
public void onClick() {
System.out.println("Windows button clicked");
}
}
class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Windows-style checkbox");
}
@Override
public void onCheck() {
System.out.println("Windows checkbox checked");
}
}
// Concrete Products for Mac
class MacButton implements Button {
@Override
public void render() {
System.out.println("Rendering Mac-style button");
}
@Override
public void onClick() {
System.out.println("Mac button clicked");
}
}
class MacCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Mac-style checkbox");
}
@Override
public void onCheck() {
System.out.println("Mac checkbox checked");
}
}
// Abstract Factory
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
// Concrete Factories
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
// Client
class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
public void render() {
button.render();
checkbox.render();
}
public void interact() {
button.onClick();
checkbox.onCheck();
}
}
// Usage
class AbstractFactoryDemo {
public static void main(String[] args) {
// Windows application
GUIFactory windowsFactory = new WindowsFactory();
Application windowsApp = new Application(windowsFactory);
windowsApp.render();
windowsApp.interact();
System.out.println();
// Mac application
GUIFactory macFactory = new MacFactory();
Application macApp = new Application(macFactory);
macApp.render();
macApp.interact();
}
}

1.4 Builder Pattern

Constructs complex objects step by step.

// Product
class Computer {
private String CPU;
private String RAM;
private String storage;
private String graphicsCard;
private String motherboard;
private Computer(ComputerBuilder builder) {
this.CPU = builder.CPU;
this.RAM = builder.RAM;
this.storage = builder.storage;
this.graphicsCard = builder.graphicsCard;
this.motherboard = builder.motherboard;
}
// Getters
public String getCPU() { return CPU; }
public String getRAM() { return RAM; }
public String getStorage() { return storage; }
public String getGraphicsCard() { return graphicsCard; }
public String getMotherboard() { return motherboard; }
@Override
public String toString() {
return "Computer{" +
"CPU='" + CPU + '\'' +
", RAM='" + RAM + '\'' +
", storage='" + storage + '\'' +
", graphicsCard='" + graphicsCard + '\'' +
", motherboard='" + motherboard + '\'' +
'}';
}
// Builder
public static class ComputerBuilder {
private String CPU;
private String RAM;
private String storage;
private String graphicsCard;
private String motherboard;
public ComputerBuilder setCPU(String CPU) {
this.CPU = CPU;
return this;
}
public ComputerBuilder setRAM(String RAM) {
this.RAM = RAM;
return this;
}
public ComputerBuilder setStorage(String storage) {
this.storage = storage;
return this;
}
public ComputerBuilder setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
public ComputerBuilder setMotherboard(String motherboard) {
this.motherboard = motherboard;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
// Usage with Director
class ComputerDirector {
public Computer buildGamingComputer() {
return new Computer.ComputerBuilder()
.setCPU("Intel i9")
.setRAM("32GB DDR5")
.setStorage("2TB NVMe SSD")
.setGraphicsCard("NVIDIA RTX 4090")
.setMotherboard("ASUS ROG")
.build();
}
public Computer buildOfficeComputer() {
return new Computer.ComputerBuilder()
.setCPU("Intel i5")
.setRAM("16GB DDR4")
.setStorage("512GB SSD")
.setGraphicsCard("Integrated")
.setMotherboard("ASUS Prime")
.build();
}
}
// Usage
class BuilderDemo {
public static void main(String[] args) {
// Custom computer
Computer customComputer = new Computer.ComputerBuilder()
.setCPU("AMD Ryzen 7")
.setRAM("16GB DDR4")
.setStorage("1TB SSD")
.setGraphicsCard("NVIDIA RTX 3060")
.setMotherboard("MSI B550")
.build();
System.out.println("Custom Computer: " + customComputer);
// Using director
ComputerDirector director = new ComputerDirector();
Computer gamingComputer = director.buildGamingComputer();
Computer officeComputer = director.buildOfficeComputer();
System.out.println("Gaming Computer: " + gamingComputer);
System.out.println("Office Computer: " + officeComputer);
}
}

1.5 Prototype Pattern

Creates new objects by copying existing objects (prototypes).

import java.util.HashMap;
import java.util.Map;
// Prototype interface
interface Prototype extends Cloneable {
Prototype clone();
void display();
}
// Concrete Prototypes
class Circle implements Prototype {
private String color;
private int x;
private int y;
private int radius;
public Circle(String color) {
this.color = color;
}
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
public void setRadius(int radius) { this.radius = radius; }
@Override
public Prototype clone() {
Circle circle = new Circle(this.color);
circle.setX(this.x);
circle.setY(this.y);
circle.setRadius(this.radius);
return circle;
}
@Override
public void display() {
System.out.println("Circle [Color: " + color + ", X: " + x + 
", Y: " + y + ", Radius: " + radius + "]");
}
}
class Rectangle implements Prototype {
private String color;
private int width;
private int height;
public Rectangle(String color) {
this.color = color;
}
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
@Override
public Prototype clone() {
Rectangle rectangle = new Rectangle(this.color);
rectangle.setWidth(this.width);
rectangle.setHeight(this.height);
return rectangle;
}
@Override
public void display() {
System.out.println("Rectangle [Color: " + color + ", Width: " + 
width + ", Height: " + height + "]");
}
}
// Prototype Registry
class ShapeRegistry {
private static Map<String, Prototype> shapes = new HashMap<>();
static {
Circle redCircle = new Circle("Red");
redCircle.setRadius(10);
shapes.put("RedCircle", redCircle);
Rectangle blueRectangle = new Rectangle("Blue");
blueRectangle.setWidth(20);
blueRectangle.setHeight(30);
shapes.put("BlueRectangle", blueRectangle);
}
public static Prototype getShape(String type) {
return shapes.get(type).clone();
}
}
// Usage
class PrototypeDemo {
public static void main(String[] args) {
// Get shapes from registry and customize them
Prototype circle1 = ShapeRegistry.getShape("RedCircle");
((Circle) circle1).setX(10);
((Circle) circle1).setY(20);
circle1.display();
Prototype circle2 = ShapeRegistry.getShape("RedCircle");
((Circle) circle2).setX(30);
((Circle) circle2).setY(40);
circle2.display();
Prototype rectangle = ShapeRegistry.getShape("BlueRectangle");
rectangle.display();
System.out.println("Are circles same object? " + (circle1 == circle2));
System.out.println("Are circles same type? " + (circle1.getClass() == circle2.getClass()));
}
}

2. Structural Patterns

2.1 Adapter Pattern

Allows incompatible interfaces to work together.

// Target Interface (Expected by Client)
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee (Existing functionality)
class AdvancedMediaPlayer {
public void playVlc(String fileName) {
System.out.println("Playing vlc file: " + fileName);
}
public void playMp4(String fileName) {
System.out.println("Playing mp4 file: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new AdvancedMediaPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new AdvancedMediaPlayer();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// Concrete Target
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
// Inbuilt support for mp3
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing mp3 file: " + fileName);
}
// MediaAdapter provides support for other formats
else if (audioType.equalsIgnoreCase("vlc") || 
audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media type: " + audioType);
}
}
}
// Usage
class AdapterDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("mp4", "video.mp4");
audioPlayer.play("vlc", "movie.vlc");
audioPlayer.play("avi", "movie.avi");
}
}

2.2 Decorator Pattern

Adds behavior to objects dynamically without affecting other objects.

// Component Interface
interface Coffee {
double getCost();
String getDescription();
}
// Concrete Component
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 2.0;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
// Decorator Abstract Class
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", milk";
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
@Override
public String getDescription() {
return super.getDescription() + ", sugar";
}
}
class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.7;
}
@Override
public String getDescription() {
return super.getDescription() + ", whipped cream";
}
}
// Usage
class DecoratorDemo {
public static void main(String[] args) {
// Simple coffee
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " $" + coffee.getCost());
// Coffee with milk
Coffee milkCoffee = new MilkDecorator(new SimpleCoffee());
System.out.println(milkCoffee.getDescription() + " $" + milkCoffee.getCost());
// Coffee with milk and sugar
Coffee milkSugarCoffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
System.out.println(milkSugarCoffee.getDescription() + " $" + milkSugarCoffee.getCost());
// Fancy coffee with everything
Coffee fancyCoffee = new WhippedCreamDecorator(
new SugarDecorator(
new MilkDecorator(new SimpleCoffee())
)
);
System.out.println(fancyCoffee.getDescription() + " $" + fancyCoffee.getCost());
}
}

2.3 Facade Pattern

Provides a simplified interface to a complex subsystem.

// Complex subsystem classes
class CPU {
public void start() {
System.out.println("CPU starting...");
}
public void execute() {
System.out.println("CPU executing commands...");
}
public void shutdown() {
System.out.println("CPU shutting down...");
}
}
class Memory {
public void load() {
System.out.println("Memory loading data...");
}
public void free() {
System.out.println("Memory freeing resources...");
}
}
class HardDrive {
public void read() {
System.out.println("HardDrive reading data...");
}
public void write() {
System.out.println("HardDrive writing data...");
}
}
// Facade
class ComputerFacade {
private CPU cpu;
private Memory memory;
private HardDrive hardDrive;
public ComputerFacade() {
this.cpu = new CPU();
this.memory = new Memory();
this.hardDrive = new HardDrive();
}
public void start() {
System.out.println("Computer starting...");
cpu.start();
memory.load();
hardDrive.read();
cpu.execute();
System.out.println("Computer started successfully!");
}
public void shutdown() {
System.out.println("Computer shutting down...");
hardDrive.write();
memory.free();
cpu.shutdown();
System.out.println("Computer shut down successfully!");
}
}
// Usage
class FacadeDemo {
public static void main(String[] args) {
ComputerFacade computer = new ComputerFacade();
// Simple interface hides complex subsystem
computer.start();
System.out.println();
computer.shutdown();
}
}

2.4 Composite Pattern

Composes objects into tree structures to represent part-whole hierarchies.

import java.util.ArrayList;
import java.util.List;
// Component
interface FileSystemComponent {
void showDetails();
long getSize();
}
// Leaf
class File implements FileSystemComponent {
private String name;
private long size;
public File(String name, long size) {
this.name = name;
this.size = size;
}
@Override
public void showDetails() {
System.out.println("File: " + name + " (" + size + " bytes)");
}
@Override
public long getSize() {
return size;
}
}
// Composite
class Directory implements FileSystemComponent {
private String name;
private List<FileSystemComponent> components;
public Directory(String name) {
this.name = name;
this.components = new ArrayList<>();
}
public void addComponent(FileSystemComponent component) {
components.add(component);
}
public void removeComponent(FileSystemComponent component) {
components.remove(component);
}
@Override
public void showDetails() {
System.out.println("Directory: " + name);
for (FileSystemComponent component : components) {
component.showDetails();
}
}
@Override
public long getSize() {
long totalSize = 0;
for (FileSystemComponent component : components) {
totalSize += component.getSize();
}
return totalSize;
}
}
// Usage
class CompositeDemo {
public static void main(String[] args) {
// Create files
File file1 = new File("document.txt", 1024);
File file2 = new File("image.jpg", 2048);
File file3 = new File("video.mp4", 4096);
// Create directories
Directory root = new Directory("Root");
Directory documents = new Directory("Documents");
Directory media = new Directory("Media");
// Build tree structure
documents.addComponent(file1);
media.addComponent(file2);
media.addComponent(file3);
root.addComponent(documents);
root.addComponent(media);
// Show details
root.showDetails();
System.out.println("Total size: " + root.getSize() + " bytes");
}
}

2.5 Proxy Pattern

Provides a surrogate or placeholder for another object to control access to it.

// Subject Interface
interface Image {
void display();
}
// Real Subject
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename);
// Simulate heavy operation
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// Proxy
class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // Lazy initialization
}
realImage.display();
}
}
// Protection Proxy Example
interface BankAccount {
void withdraw(double amount);
double getBalance();
}
class RealBankAccount implements BankAccount {
private double balance;
public RealBankAccount(double initialBalance) {
this.balance = initialBalance;
}
@Override
public void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
System.out.println("Withdrawn: $" + amount);
} else {
System.out.println("Insufficient funds!");
}
}
@Override
public double getBalance() {
return balance;
}
}
class BankAccountProxy implements BankAccount {
private RealBankAccount realAccount;
private String userRole;
public BankAccountProxy(double initialBalance, String userRole) {
this.realAccount = new RealBankAccount(initialBalance);
this.userRole = userRole;
}
@Override
public void withdraw(double amount) {
if ("admin".equals(userRole)) {
realAccount.withdraw(amount);
} else {
System.out.println("Access denied! Only admins can withdraw.");
}
}
@Override
public double getBalance() {
return realAccount.getBalance();
}
}
// Usage
class ProxyDemo {
public static void main(String[] args) {
// Virtual Proxy
System.out.println("=== Virtual Proxy ===");
Image image1 = new ProxyImage("photo1.jpg");
Image image2 = new ProxyImage("photo2.jpg");
// Image loaded only when displayed
image1.display();
image1.display(); // Already loaded
image2.display();
System.out.println("\n=== Protection Proxy ===");
BankAccount adminAccount = new BankAccountProxy(1000, "admin");
BankAccount userAccount = new BankAccountProxy(1000, "user");
adminAccount.withdraw(500);
System.out.println("Admin balance: $" + adminAccount.getBalance());
userAccount.withdraw(500); // Access denied
System.out.println("User balance: $" + userAccount.getBalance());
}
}

3. Behavioral Patterns

3.1 Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.

import java.util.ArrayList;
import java.util.List;
// Subject
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherStation() {
this.observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
private void measurementsChanged() {
notifyObservers();
}
}
// Observer
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// Concrete Observers
class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Current conditions: " + temperature + 
"°C and " + humidity + "% humidity");
}
}
class StatisticsDisplay implements Observer {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum = 0.0f;
private int numReadings;
@Override
public void update(float temperature, float humidity, float pressure) {
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) {
maxTemp = temperature;
}
if (temperature < minTemp) {
minTemp = temperature;
}
display();
}
public void display() {
System.out.println("Avg/Max/Min temperature = " + 
(tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
}
}
// Usage
class ObserverDemo {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
weatherStation.registerObserver(currentDisplay);
weatherStation.registerObserver(statisticsDisplay);
// Simulate weather changes
weatherStation.setMeasurements(25, 65, 1013);
weatherStation.setMeasurements(27, 70, 1012);
weatherStation.setMeasurements(23, 90, 1014);
// Remove one observer
weatherStation.removeObserver(statisticsDisplay);
weatherStation.setMeasurements(26, 75, 1013);
}
}

3.2 Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable.

// Strategy Interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String expiryDate;
public CreditCardPayment(String name, String cardNumber, String cvv, String expiryDate) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.expiryDate = expiryDate;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card");
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
private String password;
public PayPalPayment(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal");
}
}
class BitcoinPayment implements PaymentStrategy {
private String walletAddress;
public BitcoinPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with Bitcoin");
}
}
// Context
class ShoppingCart {
private List<String> items;
private PaymentStrategy paymentStrategy;
public ShoppingCart() {
this.items = new ArrayList<>();
}
public void addItem(String item) {
items.add(item);
}
public void removeItem(String item) {
items.remove(item);
}
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
if (paymentStrategy == null) {
System.out.println("Please set payment strategy first");
return;
}
paymentStrategy.pay(amount);
}
}
// Usage
class StrategyDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem("Laptop");
cart.addItem("Mouse");
// Pay with credit card
cart.setPaymentStrategy(new CreditCardPayment(
"John Doe", "1234-5678-9012-3456", "123", "12/25"
));
cart.checkout(1000);
// Pay with PayPal
cart.setPaymentStrategy(new PayPalPayment("[email protected]", "password"));
cart.checkout(1000);
// Pay with Bitcoin
cart.setPaymentStrategy(new BitcoinPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"));
cart.checkout(1000);
}
}

3.3 Command Pattern

Encapsulates a request as an object, thereby allowing for parameterization of clients with different requests.

// Command Interface
interface Command {
void execute();
void undo();
}
// Receiver
class Light {
public void on() {
System.out.println("Light is ON");
}
public void off() {
System.out.println("Light is OFF");
}
}
class Stereo {
public void on() {
System.out.println("Stereo is ON");
}
public void off() {
System.out.println("Stereo is OFF");
}
public void setCD() {
System.out.println("Stereo is set for CD input");
}
public void setVolume(int volume) {
System.out.println("Stereo volume set to " + volume);
}
}
// Concrete Commands
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
class StereoOnWithCDCommand implements Command {
private Stereo stereo;
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
@Override
public void undo() {
stereo.off();
}
}
// Invoker
class RemoteControl {
private Command command;
private Command lastCommand;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
lastCommand = command;
}
public void pressUndo() {
if (lastCommand != null) {
lastCommand.undo();
lastCommand = null;
}
}
}
// Usage
class CommandDemo {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();
Light livingRoomLight = new Light();
Stereo livingRoomStereo = new Stereo();
Command lightOn = new LightOnCommand(livingRoomLight);
Command lightOff = new LightOffCommand(livingRoomLight);
Command stereoOn = new StereoOnWithCDCommand(livingRoomStereo);
// Turn on light
remote.setCommand(lightOn);
remote.pressButton();
// Turn on stereo
remote.setCommand(stereoOn);
remote.pressButton();
// Undo last command (turn off stereo)
remote.pressUndo();
// Turn off light
remote.setCommand(lightOff);
remote.pressButton();
}
}

3.4 Template Method Pattern

Defines the skeleton of an algorithm in a method, deferring some steps to subclasses.

// Abstract Class
abstract class Beverage {
// Template method - defines the algorithm
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
if (customerWantsCondiments()) {
hook();
}
}
// Common steps
private void boilWater() {
System.out.println("Boiling water");
}
private void pourInCup() {
System.out.println("Pouring into cup");
}
// Abstract methods to be implemented by subclasses
abstract void brew();
abstract void addCondiments();
// Hook - optional override
protected void hook() {
// Default implementation (can be empty)
}
// Hook for conditional logic
protected boolean customerWantsCondiments() {
return true;
}
}
// Concrete Classes
class Coffee extends Beverage {
@Override
void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding sugar and milk");
}
@Override
protected boolean customerWantsCondiments() {
// Could get user input here
return true;
}
}
class Tea extends Beverage {
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding lemon");
}
@Override
protected void hook() {
System.out.println("Adding special tea flavor");
}
}
class BlackCoffee extends Beverage {
@Override
void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
void addCondiments() {
// No condiments for black coffee
}
@Override
protected boolean customerWantsCondiments() {
return false;
}
}
// Usage
class TemplateMethodDemo {
public static void main(String[] args) {
System.out.println("Preparing coffee:");
Beverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("\nPreparing tea:");
Beverage tea = new Tea();
tea.prepareRecipe();
System.out.println("\nPreparing black coffee:");
Beverage blackCoffee = new BlackCoffee();
blackCoffee.prepareRecipe();
}
}

4. Best Practices and Real-World Examples

Pattern Combinations

// Combining Factory Method with Singleton
class LoggerFactory {
private static LoggerFactory instance;
private LoggerFactory() {}
public static LoggerFactory getInstance() {
if (instance == null) {
instance = new LoggerFactory();
}
return instance;
}
public Logger getLogger(String type) {
switch (type.toLowerCase()) {
case "file":
return new FileLogger();
case "console":
return new ConsoleLogger();
case "database":
return new DatabaseLogger();
default:
throw new IllegalArgumentException("Unknown logger type: " + type);
}
}
}
interface Logger {
void log(String message);
}
class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("File Logger: " + message);
}
}
class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console Logger: " + message);
}
}
class DatabaseLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Database Logger: " + message);
}
}
// Usage
class PatternCombinationDemo {
public static void main(String[] args) {
LoggerFactory factory = LoggerFactory.getInstance();
Logger fileLogger = factory.getLogger("file");
Logger consoleLogger = factory.getLogger("console");
fileLogger.log("This is a file log message");
consoleLogger.log("This is a console log message");
}
}

Summary

Creational Patterns:

  • Singleton: Single instance with global access
  • Factory Method: Defer instantiation to subclasses
  • Abstract Factory: Create families of related objects
  • Builder: Construct complex objects step by step
  • Prototype: Create objects by copying existing ones

Structural Patterns:

  • Adapter: Make incompatible interfaces work together
  • Decorator: Add responsibilities dynamically
  • Facade: Provide a simplified interface
  • Composite: Treat individual objects and compositions uniformly
  • Proxy: Provide a placeholder for another object

Behavioral Patterns:

  • Observer: One-to-many dependency between objects
  • Strategy: Encapsulate interchangeable algorithms
  • Command: Encapsulate requests as objects
  • Template Method: Define algorithm skeleton with varying steps

When to Use Which Pattern:

  • Need single instance? → Singleton
  • Creating complex objects? → Builder
  • Need to interface with incompatible systems? → Adapter
  • Want to add features dynamically? → Decorator
  • Need to notify multiple objects? → Observer
  • Have multiple algorithms? → Strategy

Design patterns provide proven solutions to common problems and promote code reusability, maintainability, and flexibility.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper