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

Introduction

The Factory Method 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. It promotes loose coupling by eliminating the need to bind application-specific classes into the code.

Core Concept

Definition

Factory Method Pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. It lets a class defer instantiation to subclasses.

When to Use

  • When a class can't anticipate the class of objects it must create
  • When a class wants its subclasses to specify the objects it creates
  • When you want to provide a way to extend the product types without modifying existing code
  • When you need to decouple the client code from the concrete classes

Basic Implementation

1. Product Interface

// Product interface public interface Vehicle { void start(); void stop(); void drive(); } // Concrete products public class Car implements Vehicle { @Override public void start() { System.out.println("Car started"); } @Override public void stop() { System.out.println("Car stopped"); } @Override public void drive() { System.out.println("Driving car"); } } public class Motorcycle implements Vehicle { @Override public void start() { System.out.println("Motorcycle started"); } @Override public void stop() { System.out.println("Motorcycle stopped"); } @Override public void drive() { System.out.println("Riding motorcycle"); } } public class Truck implements Vehicle { @Override public void start() { System.out.println("Truck started"); } @Override public void stop() { System.out.println("Truck stopped"); } @Override public void drive() { System.out.println("Driving truck"); } }

2. Creator Abstract Class

// Creator abstract class public abstract class VehicleFactory { // Factory method - to be implemented by subclasses public abstract Vehicle createVehicle(); // Common business logic that uses the factory method public void deliverVehicle() { Vehicle vehicle = createVehicle(); vehicle.start(); vehicle.drive(); vehicle.stop(); System.out.println("Vehicle delivered to customer!"); } }

3. Concrete Creators

// Concrete creators public class CarFactory extends VehicleFactory { @Override public Vehicle createVehicle() { return new Car(); } } public class MotorcycleFactory extends VehicleFactory { @Override public Vehicle createVehicle() { return new Motorcycle(); } } public class TruckFactory extends VehicleFactory { @Override public Vehicle createVehicle() { return new Truck(); } }

4. Client Code

// Client code public class FactoryMethodDemo { public static void main(String[] args) { // Using different factories VehicleFactory carFactory = new CarFactory(); carFactory.deliverVehicle(); System.out.println("---"); VehicleFactory motorcycleFactory = new MotorcycleFactory(); motorcycleFactory.deliverVehicle(); System.out.println("---"); VehicleFactory truckFactory = new TruckFactory(); truckFactory.deliverVehicle(); } }

Advanced Implementation Variations

1. Parameterized Factory Method

// Enhanced factory with parameterized creation public abstract class EnhancedVehicleFactory { public enum VehicleType { CAR, MOTORCYCLE, TRUCK, BUS } // Factory method that can create different types public abstract Vehicle createVehicle(VehicleType type); public void deliverVehicle(VehicleType type) { Vehicle vehicle = createVehicle(type); vehicle.start(); vehicle.drive(); vehicle.stop(); System.out.println(type + " delivered to customer!"); } } // Concrete implementation with parameterized factory method public class UniversalVehicleFactory extends EnhancedVehicleFactory { @Override public Vehicle createVehicle(VehicleType type) { switch (type) { case CAR: return new Car(); case MOTORCYCLE: return new Motorcycle(); case TRUCK: return new Truck(); case BUS: return new Bus(); default: throw new IllegalArgumentException("Unknown vehicle type: " + type); } } } // Additional product public class Bus implements Vehicle { @Override public void start() { System.out.println("Bus started"); } @Override public void stop() { System.out.println("Bus stopped"); } @Override public void drive() { System.out.println("Driving bus"); } }

2. Static Factory Method

// Static factory method implementation public class StaticVehicleFactory { // Private constructor to prevent instantiation private StaticVehicleFactory() {} // Static factory methods public static Vehicle createCar() { return new Car(); } public static Vehicle createMotorcycle() { return new Motorcycle(); } public static Vehicle createTruck() { return new Truck(); } public static Vehicle createVehicle(String type) { switch (type.toLowerCase()) { case "car": return createCar(); case "motorcycle": return createMotorcycle(); case "truck": return createTruck(); default: throw new IllegalArgumentException("Unknown vehicle type: " + type); } } } // Usage of static factory public class StaticFactoryDemo { public static void main(String[] args) { Vehicle car = StaticVehicleFactory.createCar(); Vehicle motorcycle = StaticVehicleFactory.createVehicle("motorcycle"); car.start(); motorcycle.start(); } }

Real-World Examples

1. Document Processing System

// Document processing example public interface Document { void open(); void close(); void save(); void print(); } public class WordDocument implements Document { @Override public void open() { System.out.println("Opening Word document"); } @Override public void close() { System.out.println("Closing Word document"); } @Override public void save() { System.out.println("Saving Word document"); } @Override public void print() { System.out.println("Printing Word document"); } } public class PdfDocument implements Document { @Override public void open() { System.out.println("Opening PDF document"); } @Override public void close() { System.out.println("Closing PDF document"); } @Override public void save() { System.out.println("Saving PDF document"); } @Override public void print() { System.out.println("Printing PDF document"); } } public class ExcelDocument implements Document { @Override public void open() { System.out.println("Opening Excel document"); } @Override public void close() { System.out.println("Closing Excel document"); } @Override public void save() { System.out.println("Saving Excel document"); } @Override public void print() { System.out.println("Printing Excel document"); } } // Document factory public abstract class DocumentFactory { public abstract Document createDocument(); public void processDocument() { Document doc = createDocument(); doc.open(); doc.save(); doc.print(); doc.close(); } } // Concrete document factories public class WordDocumentFactory extends DocumentFactory { @Override public Document createDocument() { return new WordDocument(); } } public class PdfDocumentFactory extends DocumentFactory { @Override public Document createDocument() { return new PdfDocument(); } } public class ExcelDocumentFactory extends DocumentFactory { @Override public Document createDocument() { return new ExcelDocument(); } }

2. Database Connection Factory

// Database connection example public interface DatabaseConnection { void connect(); void disconnect(); void executeQuery(String query); } public class MySQLConnection implements DatabaseConnection { private String connectionString; public MySQLConnection(String connectionString) { this.connectionString = connectionString; } @Override public void connect() { System.out.println("Connecting to MySQL: " + connectionString); } @Override public void disconnect() { System.out.println("Disconnecting from MySQL"); } @Override public void executeQuery(String query) { System.out.println("Executing MySQL query: " + query); } } public class PostgreSQLConnection implements DatabaseConnection { private String connectionString; public PostgreSQLConnection(String connectionString) { this.connectionString = connectionString; } @Override public void connect() { System.out.println("Connecting to PostgreSQL: " + connectionString); } @Override public void disconnect() { System.out.println("Disconnecting from PostgreSQL"); } @Override public void executeQuery(String query) { System.out.println("Executing PostgreSQL query: " + query); } } public class OracleConnection implements DatabaseConnection { private String connectionString; public OracleConnection(String connectionString) { this.connectionString = connectionString; } @Override public void connect() { System.out.println("Connecting to Oracle: " + connectionString); } @Override public void disconnect() { System.out.println("Disconnecting from Oracle"); } @Override public void executeQuery(String query) { System.out.println("Executing Oracle query: " + query); } } // Database factory public abstract class DatabaseFactory { protected String connectionString; public DatabaseFactory(String connectionString) { this.connectionString = connectionString; } public abstract DatabaseConnection createConnection(); public void executeOperation(String query) { DatabaseConnection connection = createConnection(); connection.connect(); connection.executeQuery(query); connection.disconnect(); } } // Concrete database factories public class MySQLFactory extends DatabaseFactory { public MySQLFactory(String connectionString) { super(connectionString); } @Override public DatabaseConnection createConnection() { return new MySQLConnection(connectionString); } } public class PostgreSQLFactory extends DatabaseFactory { public PostgreSQLFactory(String connectionString) { super(connectionString); } @Override public DatabaseConnection createConnection() { return new PostgreSQLConnection(connectionString); } } public class OracleFactory extends DatabaseFactory { public OracleFactory(String connectionString) { super(connectionString); } @Override public DatabaseConnection createConnection() { return new OracleConnection(connectionString); } }

Advanced Patterns with Factory Method

1. Factory Method with Dependency Injection

// Configuration-based factory public interface Logger { void log(String message); void error(String message); void debug(String message); } public class FileLogger implements Logger { private String filePath; public FileLogger(String filePath) { this.filePath = filePath; } @Override public void log(String message) { System.out.println("File Log: " + message + " [File: " + filePath + "]"); } @Override public void error(String message) { System.out.println("File Error: " + message + " [File: " + filePath + "]"); } @Override public void debug(String message) { System.out.println("File Debug: " + message + " [File: " + filePath + "]"); } } public class ConsoleLogger implements Logger { @Override public void log(String message) { System.out.println("Console Log: " + message); } @Override public void error(String message) { System.err.println("Console Error: " + message); } @Override public void debug(String message) { System.out.println("Console Debug: " + message); } } public class DatabaseLogger implements Logger { private String connectionString; public DatabaseLogger(String connectionString) { this.connectionString = connectionString; } @Override public void log(String message) { System.out.println("Database Log: " + message + " [DB: " + connectionString + "]"); } @Override public void error(String message) { System.out.println("Database Error: " + message + " [DB: " + connectionString + "]"); } @Override public void debug(String message) { System.out.println("Database Debug: " + message + " [DB: " + connectionString + "]"); } } // Configuration class public class LoggerConfig { private String loggerType; private String filePath; private String dbConnectionString; // Constructors, getters, and setters public LoggerConfig(String loggerType, String filePath, String dbConnectionString) { this.loggerType = loggerType; this.filePath = filePath; this.dbConnectionString = dbConnectionString; } public String getLoggerType() { return loggerType; } public String getFilePath() { return filePath; } public String getDbConnectionString() { return dbConnectionString; } } // Configurable factory public class LoggerFactory { private LoggerConfig config; public LoggerFactory(LoggerConfig config) { this.config = config; } public Logger createLogger() { switch (config.getLoggerType().toLowerCase()) { case "file": return new FileLogger(config.getFilePath()); case "console": return new ConsoleLogger(); case "database": return new DatabaseLogger(config.getDbConnectionString()); default: throw new IllegalArgumentException("Unknown logger type: " + config.getLoggerType()); } } }

2. Factory Method with Caching

// Factory with object pooling/caching public class ConnectionPoolFactory { private Map<String, DatabaseConnection> connectionPool = new HashMap<>(); private Map<String, Integer> connectionCount = new HashMap<>(); private final int MAX_CONNECTIONS = 5; public DatabaseConnection getConnection(String dbType, String connectionString) { String key = dbType + ":" + connectionString; // Check if connection exists in pool and count is within limit if (connectionPool.containsKey(key)) { int count = connectionCount.get(key); if (count < MAX_CONNECTIONS) { connectionCount.put(key, count + 1); return connectionPool.get(key); } } // Create new connection DatabaseConnection connection = createConnection(dbType, connectionString); connectionPool.put(key, connection); connectionCount.put(key, 1); return connection; } public void releaseConnection(String dbType, String connectionString) { String key = dbType + ":" + connectionString; if (connectionCount.containsKey(key)) { int count = connectionCount.get(key) - 1; if (count <= 0) { connectionPool.remove(key); connectionCount.remove(key); } else { connectionCount.put(key, count); } } } private DatabaseConnection createConnection(String dbType, String connectionString) { switch (dbType.toLowerCase()) { case "mysql": return new MySQLConnection(connectionString); case "postgresql": return new PostgreSQLConnection(connectionString); case "oracle": return new OracleConnection(connectionString); default: throw new IllegalArgumentException("Unsupported database type: " + dbType); } } }

Testing Factory Method Pattern

Unit Testing Factories

import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; // Test classes for factory method pattern public class FactoryMethodTest { @Test public void testCarFactory() { VehicleFactory factory = new CarFactory(); Vehicle vehicle = factory.createVehicle(); assertTrue(vehicle instanceof Car); assertNotNull(vehicle); } @Test public void testMotorcycleFactory() { VehicleFactory factory = new MotorcycleFactory(); Vehicle vehicle = factory.createVehicle(); assertTrue(vehicle instanceof Motorcycle); assertNotNull(vehicle); } @Test public void testTruckFactory() { VehicleFactory factory = new TruckFactory(); Vehicle vehicle = factory.createVehicle(); assertTrue(vehicle instanceof Truck); assertNotNull(vehicle); } @Test public void testStaticFactoryMethods() { Vehicle car = StaticVehicleFactory.createCar(); Vehicle motorcycle = StaticVehicleFactory.createVehicle("motorcycle"); assertTrue(car instanceof Car); assertTrue(motorcycle instanceof Motorcycle); } @Test public void testParameterizedFactory() { UniversalVehicleFactory factory = new UniversalVehicleFactory(); Vehicle car = factory.createVehicle(EnhancedVehicleFactory.VehicleType.CAR); Vehicle truck = factory.createVehicle(EnhancedVehicleFactory.VehicleType.TRUCK); assertTrue(car instanceof Car); assertTrue(truck instanceof Truck); } @Test public void testFactoryWithInvalidType() { UniversalVehicleFactory factory = new UniversalVehicleFactory(); assertThrows(IllegalArgumentException.class, () -> { factory.createVehicle(null); }); } } // Mock objects for testing class TestVehicle implements Vehicle { private List<String> operations = new ArrayList<>(); @Override public void start() { operations.add("start"); } @Override public void stop() { operations.add("stop"); } @Override public void drive() { operations.add("drive"); } public List<String> getOperations() { return new ArrayList<>(operations); } } class TestVehicleFactory extends VehicleFactory { private TestVehicle testVehicle = new TestVehicle(); @Override public Vehicle createVehicle() { return testVehicle; } public TestVehicle getTestVehicle() { return testVehicle; } } // Test with mock factory class TestFactoryMethod { @Test public void testFactoryMethodWithMock() { TestVehicleFactory factory = new TestVehicleFactory(); factory.deliverVehicle(); TestVehicle testVehicle = factory.getTestVehicle(); List<String> operations = testVehicle.getOperations(); assertEquals(3, operations.size()); assertTrue(operations.contains("start")); assertTrue(operations.contains("drive")); assertTrue(operations.contains("stop")); } }

Best Practices and Considerations

1. When to Use Factory Method Pattern

// Good use cases: // - When the exact types of objects need to be determined at runtime // - When you want to provide a library of products that can be extended // - When you want to decouple client code from concrete implementations // - When a class wants its subclasses to specify the objects it creates public class GoodFactoryExample { // Use when you have a family of related products public abstract class UIComponentFactory { public abstract Button createButton(); public abstract TextField createTextField(); public abstract Dialog createDialog(); } // Use when object creation requires complex logic public abstract class ParserFactory { public abstract Parser createParser(File file); } }

2. When NOT to Use Factory Method Pattern

// Avoid when: // - Simple object creation doesn't need abstraction // - You only have one type of product // - The factory would just be a wrapper around constructors // - It would overcomplicate simple object creation public class BadFactoryExample { // Don't use for simple cases like this: public class SimpleObjectFactory { public SimpleObject createSimpleObject() { return new SimpleObject(); // Just calls constructor } } // Better to use direct instantiation: SimpleObject obj = new SimpleObject(); }

3. Combining with Other Patterns

// Factory Method + Singleton public class SingletonVehicleFactory { private static SingletonVehicleFactory instance; private Map<String, Vehicle> prototypes = new HashMap<>(); private SingletonVehicleFactory() { // Initialize prototypes prototypes.put("car", new Car()); prototypes.put("motorcycle", new Motorcycle()); prototypes.put("truck", new Truck()); } public static synchronized SingletonVehicleFactory getInstance() { if (instance == null) { instance = new SingletonVehicleFactory(); } return instance; } public Vehicle createVehicle(String type) { Vehicle prototype = prototypes.get(type.toLowerCase()); if (prototype != null) { // In real scenario, you might clone the prototype return createNewInstance(type); } throw new IllegalArgumentException("Unknown vehicle type: " + type); } private Vehicle createNewInstance(String type) { switch (type.toLowerCase()) { case "car": return new Car(); case "motorcycle": return new Motorcycle(); case "truck": return new Truck(); default: throw new IllegalArgumentException("Unknown type: " + type); } } } // Factory Method + Strategy public interface PricingStrategy { double calculatePrice(double basePrice); } public class RegularPricing implements PricingStrategy { @Override public double calculatePrice(double basePrice) { return basePrice; } } public class DiscountPricing implements PricingStrategy { private double discountRate; public DiscountPricing(double discountRate) { this.discountRate = discountRate; } @Override public double calculatePrice(double basePrice) { return basePrice * (1 - discountRate); } } public class PremiumPricing implements PricingStrategy { @Override public double calculatePrice(double basePrice) { return basePrice * 1.2; // 20% premium } } public class PricingStrategyFactory { public PricingStrategy createStrategy(String customerType) { switch (customerType.toLowerCase()) { case "regular": return new RegularPricing(); case "vip": return new DiscountPricing(0.1); // 10% discount case "premium": return new PremiumPricing(); default: return new RegularPricing(); } } }

Common Pitfalls and Solutions

1. Violating Open/Closed Principle

// Bad: Modifying factory when adding new products public class BadVehicleFactory { public Vehicle createVehicle(String type) { if ("car".equals(type)) { return new Car(); } else if ("motorcycle".equals(type)) { return new Motorcycle(); } // Have to modify this when adding new vehicle types throw new IllegalArgumentException("Unknown type: " + type); } } // Good: Extensible through inheritance public abstract class GoodVehicleFactory { public abstract Vehicle createVehicle(); } public class CarFactory extends GoodVehicleFactory { @Override public Vehicle createVehicle() { return new Car(); } } // Add new factory for new vehicle types without modifying existing code

2. Over-Engineering

// Bad: Unnecessary abstraction public class OverEngineeredFactory { public interface VehicleCreator { Vehicle create(); } public class CarCreator implements VehicleCreator { public Vehicle create() { return new Car(); } } // ... more unnecessary interfaces and classes } // Good: Simple and clear public class SimpleVehicleFactory { public Vehicle createCar() { return new Car(); } public Vehicle createMotorcycle() { return new Motorcycle(); } }

Conclusion

The Factory Method Pattern is a powerful creational pattern that:

  • Promotes loose coupling between client code and concrete classes
  • Supports Open/Closed Principle - easy to extend with new product types
  • Centralizes object creation logic in one place
  • Enables polymorphism through factory interfaces
  • Facilitates testing through mock factories

Key Benefits:

  1. Flexibility in object creation
  2. Maintainability through centralized creation logic
  3. Extensibility without modifying existing code
  4. Testability with mock factories
  5. Decoupling of client and product classes

Use When:

  • You need to delegate object creation to subclasses
  • You have a family of related products
  • You want to provide a way to extend your code easily
  • You need to abstract the instantiation process

The Factory Method Pattern is fundamental in Java development and forms the basis for many frameworks and libraries that require flexible object creation mechanisms.

Leave a Reply

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


Macro Nepal Helper