google-site-verification: google61fe8ba583a51912.html
Factory Pattern in Java

1. Introduction to Factory Pattern

What is Factory Pattern?

The Factory Pattern is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Why Use Factory Pattern?

  • Loose coupling between object creation and usage
  • Centralized object creation logic
  • Easy to extend with new product types
  • Promotes Open/Closed Principle
  • Simplifies complex object creation

Types of Factory Patterns:

  1. Simple Factory (Static Factory)
  2. Factory Method
  3. Abstract Factory

2. Simple Factory Pattern

// 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); } } // Simple Factory class NotificationFactory { public static Notification createNotification(String type) { switch (type.toLowerCase()) { case "email": return new EmailNotification(); case "sms": return new SMSNotification(); case "push": return new PushNotification(); default: throw new IllegalArgumentException("Unknown notification type: " + type); } } } public class SimpleFactoryExample { public static void main(String[] args) { System.out.println("=== Simple Factory Pattern ==="); // Create notifications using factory Notification email = NotificationFactory.createNotification("email"); Notification sms = NotificationFactory.createNotification("sms"); Notification push = NotificationFactory.createNotification("push"); // Use notifications email.send("Welcome to our service!"); sms.send("Your verification code is 123456"); push.send("You have a new message"); // Demonstrating flexibility System.out.println("\n=== Dynamic Notification Creation ==="); String[] notificationTypes = {"email", "sms", "push", "email"}; for (String type : notificationTypes) { try { Notification notification = NotificationFactory.createNotification(type); notification.send("Batch message via " + type.toUpperCase()); } catch (IllegalArgumentException e) { System.err.println("Error: " + e.getMessage()); } } } }

3. Complete Code Examples

Example 1: Factory Method Pattern

// Product interface interface Document { void open(); void save(); void close(); String getType(); } // Concrete Products class WordDocument implements Document { @Override public void open() { System.out.println("Opening Word document..."); } @Override public void save() { System.out.println("Saving Word document..."); } @Override public void close() { System.out.println("Closing Word document..."); } @Override public String getType() { return "Word Document"; } } class PDFDocument implements Document { @Override public void open() { System.out.println("Opening PDF document..."); } @Override public void save() { System.out.println("Saving PDF document..."); } @Override public void close() { System.out.println("Closing PDF document..."); } @Override public String getType() { return "PDF Document"; } } class ExcelDocument implements Document { @Override public void open() { System.out.println("Opening Excel document..."); } @Override public void save() { System.out.println("Saving Excel document..."); } @Override public void close() { System.out.println("Closing Excel document..."); } @Override public String getType() { return "Excel Document"; } } // Creator abstract class abstract class DocumentCreator { // Factory method - to be implemented by subclasses public abstract Document createDocument(); // Some business logic that uses the factory method public void processDocument() { Document doc = createDocument(); System.out.println("Processing " + doc.getType()); doc.open(); doc.save(); doc.close(); System.out.println(); } } // Concrete Creators class WordDocumentCreator extends DocumentCreator { @Override public Document createDocument() { return new WordDocument(); } } class PDFDocumentCreator extends DocumentCreator { @Override public Document createDocument() { return new PDFDocument(); } } class ExcelDocumentCreator extends DocumentCreator { @Override public Document createDocument() { return new ExcelDocument(); } } public class FactoryMethodExample { public static void main(String[] args) { System.out.println("=== Factory Method Pattern ==="); // Using different creators DocumentCreator[] creators = { new WordDocumentCreator(), new PDFDocumentCreator(), new ExcelDocumentCreator() }; // Process documents using factory method for (DocumentCreator creator : creators) { creator.processDocument(); } // Demonstrate creating documents directly System.out.println("=== Direct Document Creation ==="); DocumentCreator wordCreator = new WordDocumentCreator(); Document wordDoc = wordCreator.createDocument(); wordDoc.open(); wordDoc.save(); wordDoc.close(); // Factory method with parameters System.out.println("\n=== Parameterized Factory ==="); Document parameterizedDoc = createDocumentByType("pdf"); if (parameterizedDoc != null) { parameterizedDoc.open(); parameterizedDoc.save(); parameterizedDoc.close(); } } // Utility method demonstrating parameterized factory public static Document createDocumentByType(String type) { switch (type.toLowerCase()) { case "word": return new WordDocumentCreator().createDocument(); case "pdf": return new PDFDocumentCreator().createDocument(); case "excel": return new ExcelDocumentCreator().createDocument(); default: System.err.println("Unknown document type: " + type); return null; } } }

Example 2: Abstract Factory Pattern

// Abstract Products interface Button { void render(); void onClick(); } interface Checkbox { void render(); boolean isChecked(); } interface TextField { void render(); void setText(String text); String getText(); } // 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 { private boolean checked = false; @Override public void render() { System.out.println("Rendering Windows-style checkbox"); } @Override public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; System.out.println("Windows checkbox " + (checked ? "checked" : "unchecked")); } } class WindowsTextField implements TextField { private String text = ""; @Override public void render() { System.out.println("Rendering Windows-style text field"); } @Override public void setText(String text) { this.text = text; System.out.println("Windows text field set to: " + text); } @Override public String getText() { return text; } } // 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 { private boolean checked = false; @Override public void render() { System.out.println("Rendering Mac-style checkbox"); } @Override public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; System.out.println("Mac checkbox " + (checked ? "checked" : "unchecked")); } } class MacTextField implements TextField { private String text = ""; @Override public void render() { System.out.println("Rendering Mac-style text field"); } @Override public void setText(String text) { this.text = text; System.out.println("Mac text field set to: " + text); } @Override public String getText() { return text; } } // Concrete Products for Linux class LinuxButton implements Button { @Override public void render() { System.out.println("Rendering Linux-style button"); } @Override public void onClick() { System.out.println("Linux button clicked!"); } } class LinuxCheckbox implements Checkbox { private boolean checked = false; @Override public void render() { System.out.println("Rendering Linux-style checkbox"); } @Override public boolean isChecked() { return checked; } public void setChecked(boolean checked) { this.checked = checked; System.out.println("Linux checkbox " + (checked ? "checked" : "unchecked")); } } class LinuxTextField implements TextField { private String text = ""; @Override public void render() { System.out.println("Rendering Linux-style text field"); } @Override public void setText(String text) { this.text = text; System.out.println("Linux text field set to: " + text); } @Override public String getText() { return text; } } // Abstract Factory interface GUIFactory { Button createButton(); Checkbox createCheckbox(); TextField createTextField(); } // Concrete Factories class WindowsFactory implements GUIFactory { @Override public Button createButton() { return new WindowsButton(); } @Override public Checkbox createCheckbox() { return new WindowsCheckbox(); } @Override public TextField createTextField() { return new WindowsTextField(); } } class MacFactory implements GUIFactory { @Override public Button createButton() { return new MacButton(); } @Override public Checkbox createCheckbox() { return new MacCheckbox(); } @Override public TextField createTextField() { return new MacTextField(); } } class LinuxFactory implements GUIFactory { @Override public Button createButton() { return new LinuxButton(); } @Override public Checkbox createCheckbox() { return new LinuxCheckbox(); } @Override public TextField createTextField() { return new LinuxTextField(); } } // Factory provider class GUIFactoryProvider { public static GUIFactory getFactory(String osType) { switch (osType.toLowerCase()) { case "windows": return new WindowsFactory(); case "mac": return new MacFactory(); case "linux": return new LinuxFactory(); default: throw new IllegalArgumentException("Unknown OS type: " + osType); } } } // Client code class Application { private Button button; private Checkbox checkbox; private TextField textField; public Application(GUIFactory factory) { this.button = factory.createButton(); this.checkbox = factory.createCheckbox(); this.textField = factory.createTextField(); } public void renderUI() { System.out.println("\n=== Rendering UI ==="); button.render(); checkbox.render(); textField.render(); } public void simulateUserInteraction() { System.out.println("\n=== User Interaction ==="); button.onClick(); // Cast to specific type to use platform-specific methods if (checkbox instanceof WindowsCheckbox) { ((WindowsCheckbox) checkbox).setChecked(true); } else if (checkbox instanceof MacCheckbox) { ((MacCheckbox) checkbox).setChecked(true); } else if (checkbox instanceof LinuxCheckbox) { ((LinuxCheckbox) checkbox).setChecked(true); } textField.setText("Hello, World!"); System.out.println("Text field contains: " + textField.getText()); } } public class AbstractFactoryExample { public static void main(String[] args) { System.out.println("=== Abstract Factory Pattern ==="); // Test with different operating systems String[] operatingSystems = {"windows", "mac", "linux"}; for (String os : operatingSystems) { System.out.println("\n" + "=".repeat(50)); System.out.println("Creating application for: " + os.toUpperCase()); System.out.println("=".repeat(50)); try { GUIFactory factory = GUIFactoryProvider.getFactory(os); Application app = new Application(factory); app.renderUI(); app.simulateUserInteraction(); } catch (IllegalArgumentException e) { System.err.println("Error: " + e.getMessage()); } } // Demonstrate factory switching at runtime System.out.println("\n=== Runtime Factory Switching ==="); demonstrateRuntimeSwitching(); } public static void demonstrateRuntimeSwitching() { // Simulate detecting OS at runtime String detectedOS = System.getProperty("os.name").toLowerCase(); String factoryType; if (detectedOS.contains("win")) { factoryType = "windows"; } else if (detectedOS.contains("mac")) { factoryType = "mac"; } else { factoryType = "linux"; } System.out.println("Detected OS: " + detectedOS); System.out.println("Using factory: " + factoryType); GUIFactory factory = GUIFactoryProvider.getFactory(factoryType); Application app = new Application(factory); app.renderUI(); } }

Example 3: Real-World Database Connection Factory

import java.sql.*; import java.util.*; // Product interface interface DatabaseConnection { Connection getConnection() throws SQLException; void close() throws SQLException; boolean testConnection() throws SQLException; String getDatabaseType(); } // Concrete Products class MySQLConnection implements DatabaseConnection { private Connection connection; private final String url; private final String username; private final String password; public MySQLConnection(String host, int port, String database, String username, String password) { this.url = String.format("jdbc:mysql://%s:%d/%s", host, port, database); this.username = username; this.password = password; } @Override public Connection getConnection() throws SQLException { if (connection == null || connection.isClosed()) { connection = DriverManager.getConnection(url, username, password); } return connection; } @Override public void close() throws SQLException { if (connection != null && !connection.isClosed()) { connection.close(); } } @Override public boolean testConnection() throws SQLException { try (Connection conn = getConnection()) { return conn.isValid(2); // 2 second timeout } } @Override public String getDatabaseType() { return "MySQL"; } } class PostgreSQLConnection implements DatabaseConnection { private Connection connection; private final String url; private final String username; private final String password; public PostgreSQLConnection(String host, int port, String database, String username, String password) { this.url = String.format("jdbc:postgresql://%s:%d/%s", host, port, database); this.username = username; this.password = password; } @Override public Connection getConnection() throws SQLException { if (connection == null || connection.isClosed()) { connection = DriverManager.getConnection(url, username, password); } return connection; } @Override public void close() throws SQLException { if (connection != null && !connection.isClosed()) { connection.close(); } } @Override public boolean testConnection() throws SQLException { try (Connection conn = getConnection()) { return conn.isValid(2); } } @Override public String getDatabaseType() { return "PostgreSQL"; } } class OracleConnection implements DatabaseConnection { private Connection connection; private final String url; private final String username; private final String password; public OracleConnection(String host, int port, String service, String username, String password) { this.url = String.format("jdbc:oracle:thin:@%s:%d:%s", host, port, service); this.username = username; this.password = password; } @Override public Connection getConnection() throws SQLException { if (connection == null || connection.isClosed()) { connection = DriverManager.getConnection(url, username, password); } return connection; } @Override public void close() throws SQLException { if (connection != null && !connection.isClosed()) { connection.close(); } } @Override public boolean testConnection() throws SQLException { try (Connection conn = getConnection()) { return conn.isValid(2); } } @Override public String getDatabaseType() { return "Oracle"; } } // Connection Configuration class ConnectionConfig { private final String host; private final int port; private final String database; private final String username; private final String password; private final Map<String, String> properties; public ConnectionConfig(String host, int port, String database, String username, String password) { this.host = host; this.port = port; this.database = database; this.username = username; this.password = password; this.properties = new HashMap<>(); } public ConnectionConfig addProperty(String key, String value) { this.properties.put(key, value); return this; } // Getters public String getHost() { return host; } public int getPort() { return port; } public String getDatabase() { return database; } public String getUsername() { return username; } public String getPassword() { return password; } public Map<String, String> getProperties() { return new HashMap<>(properties); } } // Abstract Factory interface ConnectionFactory { DatabaseConnection createConnection(ConnectionConfig config); String getSupportedDatabase(); } // Concrete Factories class MySQLConnectionFactory implements ConnectionFactory { @Override public DatabaseConnection createConnection(ConnectionConfig config) { return new MySQLConnection( config.getHost(), config.getPort(), config.getDatabase(), config.getUsername(), config.getPassword() ); } @Override public String getSupportedDatabase() { return "MySQL"; } } class PostgreSQLConnectionFactory implements ConnectionFactory { @Override public DatabaseConnection createConnection(ConnectionConfig config) { return new PostgreSQLConnection( config.getHost(), config.getPort(), config.getDatabase(), config.getUsername(), config.getPassword() ); } @Override public String getSupportedDatabase() { return "PostgreSQL"; } } class OracleConnectionFactory implements ConnectionFactory { @Override public DatabaseConnection createConnection(ConnectionConfig config) { return new OracleConnection( config.getHost(), config.getPort(), config.getDatabase(), // Used as service name for Oracle config.getUsername(), config.getPassword() ); } @Override public String getSupportedDatabase() { return "Oracle"; } } // Factory Provider with Registry class ConnectionFactoryRegistry { private static final Map<String, ConnectionFactory> factories = new HashMap<>(); static { registerFactory("mysql", new MySQLConnectionFactory()); registerFactory("postgresql", new PostgreSQLConnectionFactory()); registerFactory("oracle", new OracleConnectionFactory()); } public static void registerFactory(String databaseType, ConnectionFactory factory) { factories.put(databaseType.toLowerCase(), factory); } public static ConnectionFactory getFactory(String databaseType) { ConnectionFactory factory = factories.get(databaseType.toLowerCase()); if (factory == null) { throw new IllegalArgumentException("Unsupported database type: " + databaseType); } return factory; } public static Set<String> getSupportedDatabases() { return new HashSet<>(factories.keySet()); } } // Connection Pool using Factory class ConnectionPool { private final ConnectionFactory factory; private final ConnectionConfig config; private final List<DatabaseConnection> availableConnections; private final List<DatabaseConnection> usedConnections; private final int maxPoolSize; public ConnectionPool(String databaseType, ConnectionConfig config, int maxPoolSize) { this.factory = ConnectionFactoryRegistry.getFactory(databaseType); this.config = config; this.maxPoolSize = maxPoolSize; this.availableConnections = new ArrayList<>(); this.usedConnections = new ArrayList<>(); initializePool(); } private void initializePool() { for (int i = 0; i < Math.min(3, maxPoolSize); i++) { // Start with 3 connections availableConnections.add(factory.createConnection(config)); } } public DatabaseConnection getConnection() throws SQLException { if (availableConnections.isEmpty()) { if (usedConnections.size() < maxPoolSize) { availableConnections.add(factory.createConnection(config)); } else { throw new SQLException("Connection pool exhausted"); } } DatabaseConnection connection = availableConnections.remove(availableConnections.size() - 1); if (!connection.testConnection()) { // Create new connection if test fails connection = factory.createConnection(config); } usedConnections.add(connection); return connection; } public void releaseConnection(DatabaseConnection connection) { usedConnections.remove(connection); availableConnections.add(connection); } public void closeAllConnections() throws SQLException { for (DatabaseConnection conn : availableConnections) { conn.close(); } for (DatabaseConnection conn : usedConnections) { conn.close(); } availableConnections.clear(); usedConnections.clear(); } public int getAvailableCount() { return availableConnections.size(); } public int getUsedCount() { return usedConnections.size(); } } public class DatabaseConnectionFactoryExample { public static void main(String[] args) { System.out.println("=== Database Connection Factory Pattern ==="); // Display supported databases System.out.println("Supported databases: " + ConnectionFactoryRegistry.getSupportedDatabases()); // Test different database connections testDatabaseConnections(); // Test connection pool testConnectionPool(); // Demonstrate dynamic factory registration demonstrateDynamicRegistration(); } public static void testDatabaseConnections() { System.out.println("\n=== Testing Database Connections ==="); // Configuration for different databases ConnectionConfig mysqlConfig = new ConnectionConfig("localhost", 3306, "testdb", "user", "password"); ConnectionConfig postgresConfig = new ConnectionConfig("localhost", 5432, "testdb", "user", "password"); ConnectionConfig oracleConfig = new ConnectionConfig("localhost", 1521, "XE", "user", "password"); String[] databases = {"mysql", "postgresql", "oracle"}; ConnectionConfig[] configs = {mysqlConfig, postgresConfig, oracleConfig}; for (int i = 0; i < databases.length; i++) { String dbType = databases[i]; ConnectionConfig config = configs[i]; System.out.println("\nTesting " + dbType.toUpperCase() + " connection:"); try { ConnectionFactory factory = ConnectionFactoryRegistry.getFactory(dbType); DatabaseConnection connection = factory.createConnection(config); System.out.println("Database type: " + connection.getDatabaseType()); System.out.println("Connection test: " + (connection.testConnection() ? "PASS" : "FAIL")); // Try to get actual connection (will fail without real database, but demonstrates pattern) try { Connection conn = connection.getConnection(); System.out.println("Connection obtained successfully"); connection.close(); } catch (SQLException e) { System.out.println("Expected connection failure (no real database): " + e.getMessage()); } } catch (Exception e) { System.err.println("Error with " + dbType + ": " + e.getMessage()); } } } public static void testConnectionPool() { System.out.println("\n=== Testing Connection Pool ==="); ConnectionConfig config = new ConnectionConfig("localhost", 3306, "testdb", "user", "password"); try (ConnectionPool pool = new ConnectionPool("mysql", config, 5)) { System.out.println("Pool created. Available connections: " + pool.getAvailableCount()); // Simulate getting connections List<DatabaseConnection> connections = new ArrayList<>(); for (int i = 0; i < 3; i++) { try { DatabaseConnection conn = pool.getConnection(); connections.add(conn); System.out.printf("Got connection %d. Available: %d, Used: %d%n", i + 1, pool.getAvailableCount(), pool.getUsedCount()); } catch (SQLException e) { System.err.println("Failed to get connection: " + e.getMessage()); } } // Release connections for (DatabaseConnection conn : connections) { pool.releaseConnection(conn); System.out.printf("Released connection. Available: %d, Used: %d%n", pool.getAvailableCount(), pool.getUsedCount()); } } catch (Exception e) { System.err.println("Pool error: " + e.getMessage()); } } public static void demonstrateDynamicRegistration() { System.out.println("\n=== Dynamic Factory Registration ==="); // Create a custom database factory ConnectionFactory sqliteFactory = new ConnectionFactory() { @Override public DatabaseConnection createConnection(ConnectionConfig config) { return new DatabaseConnection() { @Override public Connection getConnection() throws SQLException { throw new SQLException("SQLite not implemented in this example"); } @Override public void close() throws SQLException { // No-op for demo } @Override public boolean testConnection() throws SQLException { return true; // Always return true for demo } @Override public String getDatabaseType() { return "SQLite"; } }; } @Override public String getSupportedDatabase() { return "sqlite"; } }; // Register the custom factory ConnectionFactoryRegistry.registerFactory("sqlite", sqliteFactory); System.out.println("Registered custom factory for: sqlite"); System.out.println("Now supported databases: " + ConnectionFactoryRegistry.getSupportedDatabases()); // Test the custom factory try { ConnectionFactory factory = ConnectionFactoryRegistry.getFactory("sqlite"); DatabaseConnection connection = factory.createConnection( new ConnectionConfig("", 0, "test.db", "", "") ); System.out.println("Custom connection type: " + connection.getDatabaseType()); System.out.println("Custom connection test: " + (connection.testConnection() ? "PASS" : "FAIL")); } catch (Exception e) { System.err.println("Custom factory test failed: " + e.getMessage()); } } }

Example 4: Payment Processing System

import java.util.*; // Payment method interface interface PaymentMethod { boolean processPayment(double amount, String currency); boolean refund(double amount, String currency); String getPaymentMethodName(); Map<String, String> getPaymentDetails(); } // Concrete Payment Methods class CreditCardPayment implements PaymentMethod { private String cardNumber; private String cardHolder; private String expiryDate; private String cvv; public CreditCardPayment(String cardNumber, String cardHolder, String expiryDate, String cvv) { this.cardNumber = cardNumber; this.cardHolder = cardHolder; this.expiryDate = expiryDate; this.cvv = cvv; } @Override public boolean processPayment(double amount, String currency) { System.out.printf("Processing credit card payment: %.2f %s%n", amount, currency); System.out.printf("Card: %s, Holder: %s%n", maskCardNumber(cardNumber), cardHolder); // Simulate payment processing boolean success = Math.random() > 0.1; // 90% success rate for demo System.out.println("Payment " + (success ? "approved" : "declined")); return success; } @Override public boolean refund(double amount, String currency) { System.out.printf("Processing credit card refund: %.2f %s%n", amount, currency); System.out.printf("Card: %s%n", maskCardNumber(cardNumber)); // Simulate refund processing boolean success = Math.random() > 0.05; // 95% success rate for demo System.out.println("Refund " + (success ? "processed" : "failed")); return success; } @Override public String getPaymentMethodName() { return "Credit Card"; } @Override public Map<String, String> getPaymentDetails() { Map<String, String> details = new HashMap<>(); details.put("cardNumber", maskCardNumber(cardNumber)); details.put("cardHolder", cardHolder); details.put("expiryDate", expiryDate); details.put("type", "VISA"); // Simplified return details; } private String maskCardNumber(String cardNumber) { if (cardNumber.length() <= 4) return cardNumber; return "****-****-****-" + cardNumber.substring(cardNumber.length() - 4); } } class PayPalPayment implements PaymentMethod { private String email; private String transactionId; public PayPalPayment(String email) { this.email = email; } @Override public boolean processPayment(double amount, String currency) { System.out.printf("Processing PayPal payment: %.2f %s%n", amount, currency); System.out.printf("Email: %s%n", email); // Simulate PayPal processing boolean success = Math.random() > 0.05; // 95% success rate for demo if (success) { this.transactionId = "PP" + System.currentTimeMillis(); System.out.println("Payment approved. Transaction ID: " + transactionId); } else { System.out.println("Payment declined"); } return success; } @Override public boolean refund(double amount, String currency) { System.out.printf("Processing PayPal refund: %.2f %s%n", amount, currency); System.out.printf("Transaction ID: %s%n", transactionId); // Simulate refund processing boolean success = Math.random() > 0.02; // 98% success rate for demo System.out.println("Refund " + (success ? "processed" : "failed")); return success; } @Override public String getPaymentMethodName() { return "PayPal"; } @Override public Map<String, String> getPaymentDetails() { Map<String, String> details = new HashMap<>(); details.put("email", email); details.put("transactionId", transactionId != null ? transactionId : "N/A"); return details; } } class BankTransferPayment implements PaymentMethod { private String accountNumber; private String routingNumber; private String accountHolder; public BankTransferPayment(String accountNumber, String routingNumber, String accountHolder) { this.accountNumber = accountNumber; this.routingNumber = routingNumber; this.accountHolder = accountHolder; } @Override public boolean processPayment(double amount, String currency) { System.out.printf("Processing bank transfer: %.2f %s%n", amount, currency); System.out.printf("Account: %s, Holder: %s%n", maskAccountNumber(accountNumber), accountHolder); // Bank transfers are always "successful" immediately in this simulation System.out.println("Transfer initiated successfully"); return true; } @Override public boolean refund(double amount, String currency) { System.out.printf("Processing bank transfer refund: %.2f %s%n", amount, currency); System.out.println("Bank transfer refunds require manual processing"); return false; // Bank transfers typically don't support instant refunds } @Override public String getPaymentMethodName() { return "Bank Transfer"; } @Override public Map<String, String> getPaymentDetails() { Map<String, String> details = new HashMap<>(); details.put("accountNumber", maskAccountNumber(accountNumber)); details.put("routingNumber", routingNumber); details.put("accountHolder", accountHolder); return details; } private String maskAccountNumber(String accountNumber) { if (accountNumber.length() <= 4) return accountNumber; return "***" + accountNumber.substring(accountNumber.length() - 4); } } class CryptocurrencyPayment implements PaymentMethod { private String walletAddress; private String cryptocurrency; public CryptocurrencyPayment(String walletAddress, String cryptocurrency) { this.walletAddress = walletAddress; this.cryptocurrency = cryptocurrency; } @Override public boolean processPayment(double amount, String currency) { System.out.printf("Processing %s payment: %.2f %s%n", cryptocurrency, amount, currency); System.out.printf("Wallet: %s%n", maskWalletAddress(walletAddress)); // Simulate crypto transaction boolean success = Math.random() > 0.2; // 80% success rate for demo if (success) { System.out.println("Transaction confirmed on blockchain"); } else { System.out.println("Transaction failed or pending"); } return success; } @Override public boolean refund(double amount, String currency) { System.out.printf("Processing %s refund: %.2f %s%n", cryptocurrency, amount, currency); System.out.println("Cryptocurrency refunds require new transaction"); // In crypto, refunds are just new transactions return processPayment(amount, currency); } @Override public String getPaymentMethodName() { return cryptocurrency + " (Crypto)"; } @Override public Map<String, String> getPaymentDetails() { Map<String, String> details = new HashMap<>(); details.put("walletAddress", maskWalletAddress(walletAddress)); details.put("cryptocurrency", cryptocurrency); return details; } private String maskWalletAddress(String walletAddress) { if (walletAddress.length() <= 8) return walletAddress; return walletAddress.substring(0, 4) + "..." + walletAddress.substring(walletAddress.length() - 4); } } // Payment Factory class PaymentFactory { public static PaymentMethod createPaymentMethod(String type, Map<String, String> parameters) { switch (type.toLowerCase()) { case "creditcard": return new CreditCardPayment( parameters.get("cardNumber"), parameters.get("cardHolder"), parameters.get("expiryDate"), parameters.get("cvv") ); case "paypal": return new PayPalPayment(parameters.get("email")); case "banktransfer": return new BankTransferPayment( parameters.get("accountNumber"), parameters.get("routingNumber"), parameters.get("accountHolder") ); case "crypto": return new CryptocurrencyPayment( parameters.get("walletAddress"), parameters.get("cryptocurrency") ); default: throw new IllegalArgumentException("Unsupported payment method: " + type); } } } // Payment Processor class PaymentProcessor { private PaymentMethod paymentMethod; public void setPaymentMethod(PaymentMethod paymentMethod) { this.paymentMethod = paymentMethod; } public boolean processPayment(double amount, String currency) { if (paymentMethod == null) { throw new IllegalStateException("Payment method not set"); } System.out.println("\n" + "=".repeat(50)); System.out.println("PROCESSING PAYMENT"); System.out.println("=".repeat(50)); boolean success = paymentMethod.processPayment(amount, currency); if (success) { System.out.println("✅ Payment completed successfully"); } else { System.out.println("❌ Payment failed"); } return success; } public boolean processRefund(double amount, String currency) { if (paymentMethod == null) { throw new IllegalStateException("Payment method not set"); } System.out.println("\n" + "=".repeat(50)); System.out.println("PROCESSING REFUND"); System.out.println("=".repeat(50)); boolean success = paymentMethod.refund(amount, currency); if (success) { System.out.println("✅ Refund completed successfully"); } else { System.out.println("❌ Refund failed or requires manual processing"); } return success; } public void displayPaymentDetails() { if (paymentMethod != null) { System.out.println("\nPayment Method: " + paymentMethod.getPaymentMethodName()); Map<String, String> details = paymentMethod.getPaymentDetails(); details.forEach((key, value) -> System.out.printf(" %s: %s%n", key, value)); } } } public class PaymentProcessingExample { public static void main(String[] args) { System.out.println("=== Payment Processing Factory Pattern ==="); PaymentProcessor processor = new PaymentProcessor(); // Test different payment methods testCreditCardPayment(processor); testPayPalPayment(processor); testBankTransferPayment(processor); testCryptocurrencyPayment(processor); // Demonstrate dynamic payment method switching demonstrateDynamicSwitching(processor); } public static void testCreditCardPayment(PaymentProcessor processor) { System.out.println("\n" + "=".repeat(60)); System.out.println("TESTING CREDIT CARD PAYMENT"); System.out.println("=".repeat(60)); Map<String, String> params = new HashMap<>(); params.put("cardNumber", "4111111111111111"); params.put("cardHolder", "John Doe"); params.put("expiryDate", "12/25"); params.put("cvv", "123"); PaymentMethod creditCard = PaymentFactory.createPaymentMethod("creditcard", params); processor.setPaymentMethod(creditCard); processor.displayPaymentDetails(); processor.processPayment(99.99, "USD"); processor.processRefund(25.00, "USD"); } public static void testPayPalPayment(PaymentProcessor processor) { System.out.println("\n" + "=".repeat(60)); System.out.println("TESTING PAYPAL PAYMENT"); System.out.println("=".repeat(60)); Map<String, String> params = new HashMap<>(); params.put("email", "[email protected]"); PaymentMethod paypal = PaymentFactory.createPaymentMethod("paypal", params); processor.setPaymentMethod(paypal); processor.displayPaymentDetails(); processor.processPayment(49.99, "EUR"); processor.processRefund(49.99, "EUR"); } public static void testBankTransferPayment(PaymentProcessor processor) { System.out.println("\n" + "=".repeat(60)); System.out.println("TESTING BANK TRANSFER PAYMENT"); System.out.println("=".repeat(60)); Map<String, String> params = new HashMap<>(); params.put("accountNumber", "1234567890"); params.put("routingNumber", "021000021"); params.put("accountHolder", "John Doe"); PaymentMethod bankTransfer = PaymentFactory.createPaymentMethod("banktransfer", params); processor.setPaymentMethod(bankTransfer); processor.displayPaymentDetails(); processor.processPayment(199.99, "USD"); processor.processRefund(199.99, "USD"); } public static void testCryptocurrencyPayment(PaymentProcessor processor) { System.out.println("\n" + "=".repeat(60)); System.out.println("TESTING CRYPTOCURRENCY PAYMENT"); System.out.println("=".repeat(60)); Map<String, String> params = new HashMap<>(); params.put("walletAddress", "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"); params.put("cryptocurrency", "Bitcoin"); PaymentMethod crypto = PaymentFactory.createPaymentMethod("crypto", params); processor.setPaymentMethod(crypto); processor.displayPaymentDetails(); processor.processPayment(0.005, "BTC"); processor.processRefund(0.001, "BTC"); } public static void demonstrateDynamicSwitching(PaymentProcessor processor) { System.out.println("\n" + "=".repeat(60)); System.out.println("DYNAMIC PAYMENT METHOD SWITCHING"); System.out.println("=".repeat(60)); // Simulate user selecting different payment methods String[] paymentTypes = {"creditcard", "paypal", "crypto"}; double[] amounts = {75.50, 120.00, 0.0025}; String[] currencies = {"USD", "EUR", "BTC"}; for (int i = 0; i < paymentTypes.length; i++) { System.out.printf("\n--- Transaction %d: %s ---%n", i + 1, paymentTypes[i].toUpperCase()); Map<String, String> params = new HashMap<>(); switch (paymentTypes[i]) { case "creditcard": params.put("cardNumber", "5555555555554444"); params.put("cardHolder", "Jane Smith"); params.put("expiryDate", "06/26"); params.put("cvv", "456"); break; case "paypal": params.put("email", "[email protected]"); break; case "crypto": params.put("walletAddress", "0x742d35Cc6634C0532925a3b8D"); params.put("cryptocurrency", "Ethereum"); break; } try { PaymentMethod method = PaymentFactory.createPaymentMethod(paymentTypes[i], params); processor.setPaymentMethod(method); processor.displayPaymentDetails(); processor.processPayment(amounts[i], currencies[i]); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); } } } }

Example 5: Advanced Factory with Configuration and Caching

import java.util.*; import java.util.concurrent.*; // Product interface interface Cache { void put(String key, Object value); Object get(String key); void remove(String key); void clear(); int size(); String getCacheType(); Map<String, Object> getStats(); } // Concrete Products class MemoryCache implements Cache { private final Map<String, Object> storage = new ConcurrentHashMap<>(); private final Map<String, Long> accessTimes = new ConcurrentHashMap<>(); private final int maxSize; private int hitCount = 0; private int missCount = 0; public MemoryCache(int maxSize) { this.maxSize = maxSize; } @Override public void put(String key, Object value) { if (storage.size() >= maxSize) { // Simple LRU eviction evictLeastRecentlyUsed(); } storage.put(key, value); accessTimes.put(key, System.currentTimeMillis()); } @Override public Object get(String key) { Object value = storage.get(key); if (value != null) { accessTimes.put(key, System.currentTimeMillis()); hitCount++; } else { missCount++; } return value; } @Override public void remove(String key) { storage.remove(key); accessTimes.remove(key); } @Override public void clear() { storage.clear(); accessTimes.clear(); hitCount = 0; missCount = 0; } @Override public int size() { return storage.size(); } @Override public String getCacheType() { return "Memory Cache"; } @Override public Map<String, Object> getStats() { Map<String, Object> stats = new HashMap<>(); stats.put("size", size()); stats.put("maxSize", maxSize); stats.put("hitCount", hitCount); stats.put("missCount", missCount); stats.put("hitRate", hitCount + missCount > 0 ? (double) hitCount / (hitCount + missCount) : 0.0); return stats; } private void evictLeastRecentlyUsed() { String lruKey = null; long oldestTime = Long.MAX_VALUE; for (Map.Entry<String, Long> entry : accessTimes.entrySet()) { if (entry.getValue() < oldestTime) { oldestTime = entry.getValue(); lruKey = entry.getKey(); } } if (lruKey != null) { remove(lruKey); } } } class DiskCache implements Cache { private final Map<String, Object> storage = new ConcurrentHashMap<>(); private final String cacheDir; private int hitCount = 0; private int missCount = 0; public DiskCache(String cacheDir) { this.cacheDir = cacheDir; // In real implementation, this would interact with actual disk storage } @Override public void put(String key, Object value) { storage.put(key, value); // Simulate disk write System.out.println("Writing to disk: " + key); } @Override public Object get(String key) { Object value = storage.get(key); if (value != null) { hitCount++; } else { missCount++; } return value; } @Override public void remove(String key) { storage.remove(key); // Simulate disk removal System.out.println("Removing from disk: " + key); } @Override public void clear() { storage.clear(); hitCount = 0; missCount = 0; System.out.println("Cleared disk cache"); } @Override public int size() { return storage.size(); } @Override public String getCacheType() { return "Disk Cache"; } @Override public Map<String, Object> getStats() { Map<String, Object> stats = new HashMap<>(); stats.put("size", size()); stats.put("cacheDir", cacheDir); stats.put("hitCount", hitCount); stats.put("missCount", missCount); stats.put("hitRate", hitCount + missCount > 0 ? (double) hitCount / (hitCount + missCount) : 0.0); return stats; } } class RedisCache implements Cache { private final Map<String, Object> storage = new ConcurrentHashMap<>(); private final String redisUrl; private final int redisPort; private int hitCount = 0; private int missCount = 0; public RedisCache(String redisUrl, int redisPort) { this.redisUrl = redisUrl; this.redisPort = redisPort; // In real implementation, this would connect to Redis } @Override public void put(String key, Object value) { storage.put(key, value); // Simulate Redis SET operation System.out.println("Redis SET: " + key); } @Override public Object get(String key) { Object value = storage.get(key); if (value != null) { hitCount++; } else { missCount++; } return value; } @Override public void remove(String key) { storage.remove(key); // Simulate Redis DEL operation System.out.println("Redis DEL: " + key); } @Override public void clear() { storage.clear(); hitCount = 0; missCount = 0; System.out.println("Redis FLUSHDB"); } @Override public int size() { return storage.size(); } @Override public String getCacheType() { return "Redis Cache"; } @Override public Map<String, Object> getStats() { Map<String, Object> stats = new HashMap<>(); stats.put("size", size()); stats.put("redisUrl", redisUrl); stats.put("redisPort", redisPort); stats.put("hitCount", hitCount); stats.put("missCount", missCount); stats.put("hitRate", hitCount + missCount > 0 ? (double) hitCount / (hitCount + missCount) : 0.0); return stats; } } // Cache Configuration class CacheConfig { private final String cacheType; private final Map<String, Object> properties; public CacheConfig(String cacheType) { this.cacheType = cacheType; this.properties = new HashMap<>(); } public CacheConfig setProperty(String key, Object value) { properties.put(key, value); return this; } public String getCacheType() { return cacheType; } public Map<String, Object> getProperties() { return new HashMap<>(properties); } public <T> T getProperty(String key, T defaultValue) { return properties.containsKey(key) ? (T) properties.get(key) : defaultValue; } } // Abstract Factory interface CacheFactory { Cache createCache(CacheConfig config); boolean supportsType(String cacheType); } // Concrete Factories class MemoryCacheFactory implements CacheFactory { @Override public Cache createCache(CacheConfig config) { int maxSize = config.getProperty("maxSize", 1000); return new MemoryCache(maxSize); } @Override public boolean supportsType(String cacheType) { return "memory".equalsIgnoreCase(cacheType); } } class DiskCacheFactory implements CacheFactory { @Override public Cache createCache(CacheConfig config) { String cacheDir = config.getProperty("cacheDir", "/tmp/cache"); return new DiskCache(cacheDir); } @Override public boolean supportsType(String cacheType) { return "disk".equalsIgnoreCase(cacheType); } } class RedisCacheFactory implements CacheFactory { @Override public Cache createCache(CacheConfig config) { String redisUrl = config.getProperty("redisUrl", "localhost"); int redisPort = config.getProperty("redisPort", 6379); return new RedisCache(redisUrl, redisPort); } @Override public boolean supportsType(String cacheType) { return "redis".equalsIgnoreCase(cacheType); } } // Factory Manager with Registration class CacheFactoryManager { private static final Map<String, CacheFactory> factories = new HashMap<>(); private static final Map<String, Cache> cacheInstances = new ConcurrentHashMap<>(); static { // Register default factories registerFactory("memory", new MemoryCacheFactory()); registerFactory("disk", new DiskCacheFactory()); registerFactory("redis", new RedisCacheFactory()); } public static void registerFactory(String cacheType, CacheFactory factory) { factories.put(cacheType.toLowerCase(), factory); } public static Cache createCache(CacheConfig config) { String cacheType = config.getCacheType().toLowerCase(); CacheFactory factory = factories.get(cacheType); if (factory == null) { throw new IllegalArgumentException("Unsupported cache type: " + cacheType); } return factory.createCache(config); } public static Cache getOrCreateCache(String cacheName, CacheConfig config) { return cacheInstances.computeIfAbsent(cacheName, k -> createCache(config)); } public static Set<String> getSupportedCacheTypes() { return new HashSet<>(factories.keySet()); } public static void clearCacheInstances() { cacheInstances.values().forEach(Cache::clear); cacheInstances.clear(); } } // Cache Manager class CacheManager { private final Map<String, Cache> caches = new ConcurrentHashMap<>(); public Cache createCache(String name, CacheConfig config) { Cache cache = CacheFactoryManager.createCache(config); caches.put(name, cache); System.out.println("Created cache: " + name + " (" + cache.getCacheType() + ")"); return cache; } public Cache getCache(String name) { return caches.get(name); } public void removeCache(String name) { Cache cache = caches.remove(name); if (cache != null) { cache.clear(); } } public Set<String> getCacheNames() { return new HashSet<>(caches.keySet()); } public void displayStats() { System.out.println("\n=== Cache Statistics ==="); caches.forEach((name, cache) -> { System.out.println("\nCache: " + name + " (" + cache.getCacheType() + ")"); Map<String, Object> stats = cache.getStats(); stats.forEach((key, value) -> System.out.printf(" %s: %s%n", key, value)); }); } } public class AdvancedCacheFactoryExample { public static void main(String[] args) { System.out.println("=== Advanced Cache Factory Pattern ==="); // Display supported cache types System.out.println("Supported cache types: " + CacheFactoryManager.getSupportedCacheTypes()); CacheManager cacheManager = new CacheManager(); // Create different types of caches createVariousCaches(cacheManager); // Test cache operations testCacheOperations(cacheManager); // Demonstrate cache statistics cacheManager.displayStats(); // Test performance testCachePerformance(); // Clean up CacheFactoryManager.clearCacheInstances(); } public static void createVariousCaches(CacheManager cacheManager) { System.out.println("\n=== Creating Various Caches ==="); // Memory cache with small size CacheConfig memoryConfig = new CacheConfig("memory") .setProperty("maxSize", 100); cacheManager.createCache("smallMemoryCache", memoryConfig); // Memory cache with large size CacheConfig largeMemoryConfig = new CacheConfig("memory") .setProperty("maxSize", 10000); cacheManager.createCache("largeMemoryCache", largeMemoryConfig); // Disk cache CacheConfig diskConfig = new CacheConfig("disk") .setProperty("cacheDir", "/var/cache/myapp"); cacheManager.createCache("diskCache", diskConfig); // Redis cache CacheConfig redisConfig = new CacheConfig("redis") .setProperty("redisUrl", "redis.example.com") .setProperty("redisPort", 6379); cacheManager.createCache("redisCache", redisConfig); System.out.println("Created caches: " + cacheManager.getCacheNames()); } public static void testCacheOperations(CacheManager cacheManager) { System.out.println("\n=== Testing Cache Operations ==="); Cache memoryCache = cacheManager.getCache("smallMemoryCache"); if (memoryCache != null) { // Basic operations memoryCache.put("key1", "value1"); memoryCache.put("key2", 12345); memoryCache.put("key3", Arrays.asList("a", "b", "c")); System.out.println("Cache size: " + memoryCache.size()); System.out.println("Get key1: " + memoryCache.get("key1")); System.out.println("Get key2: " + memoryCache.get("key2")); System.out.println("Get non-existent key: " + memoryCache.get("key999")); // Test eviction by adding more items for (int i = 0; i < 150; i++) { memoryCache.put("testKey" + i, "testValue" + i); } System.out.println("Cache size after adding 150 items: " + memoryCache.size()); // Test multiple caches Cache largeMemoryCache = cacheManager.getCache("largeMemoryCache"); if (largeMemoryCache != null) { largeMemoryCache.put("largeKey", "This is in large cache"); System.out.println("Large cache get: " + largeMemoryCache.get("largeKey")); } } } public static void testCachePerformance() { System.out.println("\n=== Cache Performance Test ==="); CacheConfig config = new CacheConfig("memory") .setProperty("maxSize", 5000); Cache cache = CacheFactoryManager.createCache(config); int operations = 1000; long startTime, endTime; // Write performance startTime = System.currentTimeMillis(); for (int i = 0; i < operations; i++) { cache.put("perfKey" + i, "perfValue" + i); } endTime = System.currentTimeMillis(); System.out.printf("Write %d operations: %d ms%n", operations, (endTime - startTime)); // Read performance startTime = System.currentTimeMillis(); for (int i = 0; i < operations; i++) { cache.get("perfKey" + i); } endTime = System.currentTimeMillis(); System.out.printf("Read %d operations: %d ms%n", operations, (endTime - startTime)); // Mixed operations startTime = System.currentTimeMillis(); for (int i = 0; i < operations; i++) { if (i % 2 == 0) { cache.put("mixedKey" + i, "mixedValue" + i); } else { cache.get("mixedKey" + (i - 1)); } } endTime = System.currentTimeMillis(); System.out.printf("Mixed %d operations: %d ms%n", operations, (endTime - startTime)); cache.clear(); } }

9. Conclusion

Key Takeaways:

  1. Simple Factory: Static method that creates objects based on parameters
  2. Factory Method: Subclasses decide which class to instantiate
  3. Abstract Factory: Families of related objects without specifying concrete classes

When to Use Factory Pattern:

  • Object creation logic is complex
  • Need to decouple object creation from usage
  • System should be independent of how objects are created
  • Want to provide a library of objects that can be extended
  • Need to create families of related objects

Benefits:

  • Loose coupling between client and concrete classes
  • Single Responsibility Principle - creation logic in one place
  • Open/Closed Principle - easy to introduce new types
  • Code organization and maintainability
  • Configuration flexibility

Common Use Cases:

  • Database connections and connection pools
  • Logger frameworks with different appenders
  • UI toolkits with different look-and-feels
  • Payment processors with different payment methods
  • Cache systems with different storage backends

Best Practices:

  • Use meaningful names for factory methods
  • Consider using configuration files for factory setup
  • Implement proper error handling for creation failures
  • Use dependency injection with factories for better testability
  • Consider caching for expensive object creation

Comparison:

PatternUse CaseComplexityFlexibility
Simple FactorySimple object creationLowLimited
Factory MethodSubclass-specific creationMediumHigh
Abstract FactoryFamilies of related objectsHighVery High

Final Thoughts:

The Factory Pattern is one of the most widely used creational patterns in Java. It provides:

  • Clean separation of object creation and business logic
  • Enhanced testability through dependency injection
  • Improved maintainability through centralized creation logic
  • Greater flexibility for future extensions

Master the Factory Pattern to write more flexible, maintainable, and testable Java applications!

Leave a Reply

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


Macro Nepal Helper