google-site-verification: google61fe8ba583a51912.html
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