Java Programming Hub

Advanced Java development tutorials and guides

Design PatternsJavaSoftware ArchitectureBest PracticesOOP

Java Design Patterns: Essential Patterns for Enterprise Applications

January 7, 202518 min readDesign Patterns
# Java Design Patterns: Essential Patterns for Enterprise Applications Design patterns are proven solutions to recurring design problems in software development. Understanding and applying these patterns correctly can significantly improve code quality, maintainability, and team communication. This guide covers essential patterns for Java enterprise applications. ## Creational Patterns Creational patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. ### Singleton Pattern Ensures a class has only one instance and provides global access to it: ```java // Thread-safe Singleton with enum (recommended) public enum DatabaseConnection { INSTANCE; private Connection connection; DatabaseConnection() { // Initialize connection try { this.connection = DriverManager.getConnection( "jdbc:postgresql://localhost:5432/mydb", "username", "password" ); } catch (SQLException e) { throw new RuntimeException("Failed to create database connection", e); } } public Connection getConnection() { return connection; } public void executeQuery(String sql) { // Execute query using connection } } // Thread-safe Singleton with double-checked locking public class ConfigurationManager { private static volatile ConfigurationManager instance; private final Properties properties; private ConfigurationManager() { properties = new Properties(); loadConfiguration(); } public static ConfigurationManager getInstance() { if (instance == null) { synchronized (ConfigurationManager.class) { if (instance == null) { instance = new ConfigurationManager(); } } } return instance; } private void loadConfiguration() { try (InputStream input = getClass().getResourceAsStream("/application.properties")) { properties.load(input); } catch (IOException e) { throw new RuntimeException("Failed to load configuration", e); } } public String getProperty(String key) { return properties.getProperty(key); } public String getProperty(String key, String defaultValue) { return properties.getProperty(key, defaultValue); } } // Usage DatabaseConnection.INSTANCE.executeQuery("SELECT * FROM users"); ConfigurationManager config = ConfigurationManager.getInstance(); String dbUrl = config.getProperty("database.url"); ``` ### Factory Pattern Creates objects without specifying their exact classes: ```java // Product interface public interface PaymentProcessor { void processPayment(BigDecimal amount, String currency); boolean validatePayment(PaymentDetails details); PaymentResult getPaymentStatus(String transactionId); } // Concrete products public class CreditCardProcessor implements PaymentProcessor { @Override public void processPayment(BigDecimal amount, String currency) { System.out.println("Processing credit card payment: " + amount + " " + currency); // Credit card specific logic } @Override public boolean validatePayment(PaymentDetails details) { // Validate credit card details return details.getCardNumber() != null && details.getCvv() != null; } @Override public PaymentResult getPaymentStatus(String transactionId) { // Get credit card payment status return new PaymentResult(transactionId, PaymentStatus.COMPLETED); } } public class PayPalProcessor implements PaymentProcessor { @Override public void processPayment(BigDecimal amount, String currency) { System.out.println("Processing PayPal payment: " + amount + " " + currency); // PayPal specific logic } @Override public boolean validatePayment(PaymentDetails details) { // Validate PayPal details return details.getEmail() != null; } @Override public PaymentResult getPaymentStatus(String transactionId) { // Get PayPal payment status return new PaymentResult(transactionId, PaymentStatus.PENDING); } } public class BankTransferProcessor implements PaymentProcessor { @Override public void processPayment(BigDecimal amount, String currency) { System.out.println("Processing bank transfer: " + amount + " " + currency); // Bank transfer specific logic } @Override public boolean validatePayment(PaymentDetails details) { // Validate bank details return details.getAccountNumber() != null && details.getRoutingNumber() != null; } @Override public PaymentResult getPaymentStatus(String transactionId) { // Get bank transfer status return new PaymentResult(transactionId, PaymentStatus.PROCESSING); } } // Factory public class PaymentProcessorFactory { public static PaymentProcessor createProcessor(PaymentType type) { return switch (type) { case CREDIT_CARD -> new CreditCardProcessor(); case PAYPAL -> new PayPalProcessor(); case BANK_TRANSFER -> new BankTransferProcessor(); default -> throw new IllegalArgumentException("Unsupported payment type: " + type); }; } } // Abstract Factory for different regions public interface PaymentProcessorAbstractFactory { PaymentProcessor createCreditCardProcessor(); PaymentProcessor createDigitalWalletProcessor(); PaymentProcessor createBankTransferProcessor(); } public class USPaymentProcessorFactory implements PaymentProcessorAbstractFactory { @Override public PaymentProcessor createCreditCardProcessor() { return new USCreditCardProcessor(); } @Override public PaymentProcessor createDigitalWalletProcessor() { return new PayPalProcessor(); } @Override public PaymentProcessor createBankTransferProcessor() { return new ACHProcessor(); } } public class EUPaymentProcessorFactory implements PaymentProcessorAbstractFactory { @Override public PaymentProcessor createCreditCardProcessor() { return new EUCreditCardProcessor(); } @Override public PaymentProcessor createDigitalWalletProcessor() { return new PayPalProcessor(); } @Override public PaymentProcessor createBankTransferProcessor() { return new SEPAProcessor(); } } // Usage PaymentProcessor processor = PaymentProcessorFactory.createProcessor(PaymentType.CREDIT_CARD); processor.processPayment(new BigDecimal("100.00"), "USD"); PaymentProcessorAbstractFactory factory = new USPaymentProcessorFactory(); PaymentProcessor creditCardProcessor = factory.createCreditCardProcessor(); ``` ### Builder Pattern Constructs complex objects step by step: ```java // Product class public class User { private final String firstName; private final String lastName; private final String email; private final String phone; private final Address address; private final List<String> roles; private final Map<String, String> preferences; private final boolean active; private final LocalDateTime createdDate; private User(Builder builder) { this.firstName = builder.firstName; this.lastName = builder.lastName; this.email = builder.email; this.phone = builder.phone; this.address = builder.address; this.roles = Collections.unmodifiableList(new ArrayList<>(builder.roles)); this.preferences = Collections.unmodifiableMap(new HashMap<>(builder.preferences)); this.active = builder.active; this.createdDate = builder.createdDate; } // Getters public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public String getEmail() { return email; } public String getPhone() { return phone; } public Address getAddress() { return address; } public List<String> getRoles() { return roles; } public Map<String, String> getPreferences() { return preferences; } public boolean isActive() { return active; } public LocalDateTime getCreatedDate() { return createdDate; } public static Builder builder() { return new Builder(); } public static class Builder { private String firstName; private String lastName; private String email; private String phone; private Address address; private List<String> roles = new ArrayList<>(); private Map<String, String> preferences = new HashMap<>(); private boolean active = true; private LocalDateTime createdDate = LocalDateTime.now(); public Builder firstName(String firstName) { this.firstName = firstName; return this; } public Builder lastName(String lastName) { this.lastName = lastName; return this; } public Builder email(String email) { this.email = email; return this; } public Builder phone(String phone) { this.phone = phone; return this; } public Builder address(Address address) { this.address = address; return this; } public Builder addRole(String role) { this.roles.add(role); return this; } public Builder roles(List<String> roles) { this.roles = new ArrayList<>(roles); return this; } public Builder addPreference(String key, String value) { this.preferences.put(key, value); return this; } public Builder preferences(Map<String, String> preferences) { this.preferences = new HashMap<>(preferences); return this; } public Builder active(boolean active) { this.active = active; return this; } public Builder createdDate(LocalDateTime createdDate) { this.createdDate = createdDate; return this; } public User build() { validate(); return new User(this); } private void validate() { if (firstName == null || firstName.trim().isEmpty()) { throw new IllegalArgumentException("First name is required"); } if (lastName == null || lastName.trim().isEmpty()) { throw new IllegalArgumentException("Last name is required"); } if (email == null || !email.contains("@")) { throw new IllegalArgumentException("Valid email is required"); } } } } // Usage User user = User.builder() .firstName("John") .lastName("Doe") .email("john.doe@example.com") .phone("+1-555-123-4567") .address(new Address("123 Main St", "Anytown", "ST", "12345")) .addRole("USER") .addRole("ADMIN") .addPreference("theme", "dark") .addPreference("language", "en") .active(true) .build(); ``` ## Structural Patterns Structural patterns deal with object composition and relationships between objects. ### Adapter Pattern Allows incompatible interfaces to work together: ```java // Target interface (what client expects) public interface MediaPlayer { void play(String audioType, String fileName); } // Adaptee (existing class with incompatible interface) public 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 public class MediaAdapter implements MediaPlayer { private AdvancedMediaPlayer advancedPlayer; public MediaAdapter(String audioType) { if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) { advancedPlayer = new AdvancedMediaPlayer(); } } @Override public void play(String audioType, String fileName) { if ("vlc".equalsIgnoreCase(audioType)) { advancedPlayer.playVlc(fileName); } else if ("mp4".equalsIgnoreCase(audioType)) { advancedPlayer.playMp4(fileName); } } } // Client public class AudioPlayer implements MediaPlayer { private MediaAdapter mediaAdapter; @Override public void play(String audioType, String fileName) { if ("mp3".equalsIgnoreCase(audioType)) { System.out.println("Playing mp3 file: " + fileName); } else if ("vlc".equalsIgnoreCase(audioType) || "mp4".equalsIgnoreCase(audioType)) { mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported"); } } } // Real-world example: Database adapter public interface DatabaseConnection { void connect(); void disconnect(); ResultSet executeQuery(String query); int executeUpdate(String query); } public class MySQLConnection { public void establishConnection() { /* MySQL specific */ } public void closeConnection() { /* MySQL specific */ } public MySQLResultSet runQuery(String sql) { /* MySQL specific */ } public int runUpdate(String sql) { /* MySQL specific */ } } public class PostgreSQLConnection { public void openConnection() { /* PostgreSQL specific */ } public void terminateConnection() { /* PostgreSQL specific */ } public PostgreSQLResultSet performQuery(String sql) { /* PostgreSQL specific */ } public int performUpdate(String sql) { /* PostgreSQL specific */ } } public class MySQLAdapter implements DatabaseConnection { private MySQLConnection mysqlConnection; public MySQLAdapter(MySQLConnection mysqlConnection) { this.mysqlConnection = mysqlConnection; } @Override public void connect() { mysqlConnection.establishConnection(); } @Override public void disconnect() { mysqlConnection.closeConnection(); } @Override public ResultSet executeQuery(String query) { MySQLResultSet mysqlResult = mysqlConnection.runQuery(query); return new ResultSetAdapter(mysqlResult); } @Override public int executeUpdate(String query) { return mysqlConnection.runUpdate(query); } } ``` ### Decorator Pattern Adds new functionality to objects dynamically without altering their structure: ```java // Component interface public interface Coffee { String getDescription(); double getCost(); } // Concrete component public class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple coffee"; } @Override public double getCost() { return 2.0; } } // Base decorator public abstract class CoffeeDecorator implements Coffee { protected Coffee coffee; public CoffeeDecorator(Coffee coffee) { this.coffee = coffee; } @Override public String getDescription() { return coffee.getDescription(); } @Override public double getCost() { return coffee.getCost(); } } // Concrete decorators public class MilkDecorator extends CoffeeDecorator { public MilkDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return coffee.getDescription() + ", milk"; } @Override public double getCost() { return coffee.getCost() + 0.5; } } public class SugarDecorator extends CoffeeDecorator { public SugarDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return coffee.getDescription() + ", sugar"; } @Override public double getCost() { return coffee.getCost() + 0.2; } } public class WhipDecorator extends CoffeeDecorator { public WhipDecorator(Coffee coffee) { super(coffee); } @Override public String getDescription() { return coffee.getDescription() + ", whip"; } @Override public double getCost() { return coffee.getCost() + 0.7; } } // Usage Coffee coffee = new SimpleCoffee(); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); coffee = new MilkDecorator(coffee); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); coffee = new SugarDecorator(coffee); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); coffee = new WhipDecorator(coffee); System.out.println(coffee.getDescription() + " $" + coffee.getCost()); // Real-world example: Stream decorators public interface DataSource { void writeData(String data); String readData(); } public class FileDataSource implements DataSource { private String filename; public FileDataSource(String filename) { this.filename = filename; } @Override public void writeData(String data) { // Write data to file try (FileWriter writer = new FileWriter(filename)) { writer.write(data); } catch (IOException e) { throw new RuntimeException("Error writing to file", e); } } @Override public String readData() { // Read data from file try { return Files.readString(Paths.get(filename)); } catch (IOException e) { throw new RuntimeException("Error reading from file", e); } } } public abstract class DataSourceDecorator implements DataSource { protected DataSource wrappee; public DataSourceDecorator(DataSource source) { this.wrappee = source; } @Override public void writeData(String data) { wrappee.writeData(data); } @Override public String readData() { return wrappee.readData(); } } public class EncryptionDecorator extends DataSourceDecorator { public EncryptionDecorator(DataSource source) { super(source); } @Override public void writeData(String data) { super.writeData(encrypt(data)); } @Override public String readData() { return decrypt(super.readData()); } private String encrypt(String data) { // Simple encryption (in real world, use proper encryption) return Base64.getEncoder().encodeToString(data.getBytes()); } private String decrypt(String data) { return new String(Base64.getDecoder().decode(data)); } } public class CompressionDecorator extends DataSourceDecorator { public CompressionDecorator(DataSource source) { super(source); } @Override public void writeData(String data) { super.writeData(compress(data)); } @Override public String readData() { return decompress(super.readData()); } private String compress(String data) { // Simple compression simulation return data.replaceAll("\s+", " "); } private String decompress(String data) { // Simple decompression simulation return data; } } // Usage DataSource source = new FileDataSource("data.txt"); source = new EncryptionDecorator(source); source = new CompressionDecorator(source); source.writeData("Hello World! This is a test message."); String data = source.readData(); ``` ### Facade Pattern Provides a simplified interface to a complex subsystem: ```java // Complex subsystem classes public class CPU { public void freeze() { System.out.println("CPU: Freezing processor"); } public void jump(long position) { System.out.println("CPU: Jumping to position " + position); } public void execute() { System.out.println("CPU: Executing instructions"); } } public class Memory { public void load(long position, String data) { System.out.println("Memory: Loading data '" + data + "' at position " + position); } } public class HardDrive { public String read(long lba, int size) { System.out.println("HardDrive: Reading " + size + " bytes from LBA " + lba); return "boot data"; } } // Facade public 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("Starting computer..."); cpu.freeze(); memory.load(0, hardDrive.read(0, 1024)); cpu.jump(0); cpu.execute(); System.out.println("Computer started successfully!"); } } // Real-world example: Order processing facade public class InventoryService { public boolean checkAvailability(String productId, int quantity) { System.out.println("Checking inventory for " + productId + " quantity: " + quantity); return true; } public void reserveItems(String productId, int quantity) { System.out.println("Reserving " + quantity + " items of " + productId); } } public class PaymentService { public boolean processPayment(String customerId, BigDecimal amount) { System.out.println("Processing payment of $" + amount + " for customer " + customerId); return true; } } public class ShippingService { public String scheduleShipping(String customerId, String address) { System.out.println("Scheduling shipping to " + address + " for customer " + customerId); return "SHIP123456"; } } public class NotificationService { public void sendOrderConfirmation(String customerId, String orderId) { System.out.println("Sending order confirmation for order " + orderId + " to customer " + customerId); } } // Facade for order processing public class OrderProcessingFacade { private InventoryService inventoryService; private PaymentService paymentService; private ShippingService shippingService; private NotificationService notificationService; public OrderProcessingFacade() { this.inventoryService = new InventoryService(); this.paymentService = new PaymentService(); this.shippingService = new ShippingService(); this.notificationService = new NotificationService(); } public OrderResult processOrder(OrderRequest orderRequest) { try { // Step 1: Check inventory if (!inventoryService.checkAvailability(orderRequest.getProductId(), orderRequest.getQuantity())) { return OrderResult.failure("Product not available"); } // Step 2: Process payment if (!paymentService.processPayment(orderRequest.getCustomerId(), orderRequest.getAmount())) { return OrderResult.failure("Payment failed"); } // Step 3: Reserve inventory inventoryService.reserveItems(orderRequest.getProductId(), orderRequest.getQuantity()); // Step 4: Schedule shipping String trackingNumber = shippingService.scheduleShipping( orderRequest.getCustomerId(), orderRequest.getShippingAddress() ); // Step 5: Send confirmation String orderId = generateOrderId(); notificationService.sendOrderConfirmation(orderRequest.getCustomerId(), orderId); return OrderResult.success(orderId, trackingNumber); } catch (Exception e) { return OrderResult.failure("Order processing failed: " + e.getMessage()); } } private String generateOrderId() { return "ORD" + System.currentTimeMillis(); } } // Usage ComputerFacade computer = new ComputerFacade(); computer.start(); OrderProcessingFacade orderProcessor = new OrderProcessingFacade(); OrderRequest request = new OrderRequest("CUST123", "PROD456", 2, new BigDecimal("99.99"), "123 Main St"); OrderResult result = orderProcessor.processOrder(request); ``` Design patterns provide proven solutions to common programming problems. Understanding when and how to apply these patterns is crucial for writing maintainable, flexible, and robust Java applications. The key is to use patterns judiciously - not every problem requires a pattern, and overuse can lead to unnecessary complexity.