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:
- Flexibility in object creation
- Maintainability through centralized creation logic
- Extensibility without modifying existing code
- Testability with mock factories
- 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.