Factory Method Pattern: Object Creation Delegation

Every time you write `new ConcreteClass()`, you're welding your code to that specific implementation. This seems harmless in small applications, but it creates brittle architectures that resist...

Key Insights

  • Factory Method delegates object creation to subclasses, enabling frameworks to define interfaces while letting applications specify concrete implementations
  • The pattern shines when you need to support multiple product variants or when the exact type of object depends on runtime conditions that subclasses understand best
  • Unlike Simple Factory (a static method with conditionals), Factory Method uses inheritance and polymorphism to achieve true extensibility without modifying existing code

The Problem with Direct Instantiation

Every time you write new ConcreteClass(), you’re welding your code to that specific implementation. This seems harmless in small applications, but it creates brittle architectures that resist change.

Consider a reporting system that generates PDF documents:

public class ReportService {
    public void generateReport(ReportData data) {
        PDFDocument document = new PDFDocument();
        document.addHeader(data.getTitle());
        document.addContent(data.getBody());
        document.save();
    }
}

This works until a client needs Word documents. Then another needs HTML. Suddenly you’re adding conditionals, passing format parameters, and watching your clean method turn into a mess of branching logic. The ReportService now knows about every document type, violating the Single Responsibility Principle and making testing painful.

The core issue: creation logic is tangled with business logic. When object creation becomes complex—involving configuration, conditional decisions, or coordination of multiple objects—you need a dedicated mechanism to handle it.

Pattern Definition and Structure

The Factory Method pattern defines an interface for creating objects but lets subclasses decide which class to instantiate. The pattern defers instantiation to subclasses, allowing a class to delegate responsibility to one of several helper subclasses.

The structure involves four participants:

  • Product: The interface or abstract class defining objects the factory method creates
  • ConcreteProduct: Specific implementations of the Product interface
  • Creator: Declares the factory method, which returns a Product object. May also define a default implementation
  • ConcreteCreator: Overrides the factory method to return a ConcreteProduct instance

This differs fundamentally from Simple Factory, which is just a static method with conditionals. Simple Factory centralizes creation logic but doesn’t provide extensibility—adding new products means modifying the factory. Factory Method uses polymorphism, allowing new products through new subclasses without touching existing code.

Here’s the basic structure:

// Product interface
public interface Document {
    void open();
    void addContent(String content);
    void save();
}

// Creator with factory method
public abstract class DocumentCreator {
    // The factory method
    public abstract Document createDocument();
    
    // Business logic that uses the factory method
    public void generateReport(ReportData data) {
        Document doc = createDocument();
        doc.open();
        doc.addContent(data.getBody());
        doc.save();
    }
}

Implementation Walkthrough

Let’s build a complete document generation system. First, define the product hierarchy:

public interface Document {
    void open();
    void addHeader(String header);
    void addContent(String content);
    void save();
    String getExtension();
}

public class PDFDocument implements Document {
    private StringBuilder content = new StringBuilder();
    
    @Override
    public void open() {
        System.out.println("Creating new PDF document");
    }
    
    @Override
    public void addHeader(String header) {
        content.append("[PDF HEADER] ").append(header).append("\n");
    }
    
    @Override
    public void addContent(String text) {
        content.append(text);
    }
    
    @Override
    public void save() {
        System.out.println("Saving PDF with compression...");
    }
    
    @Override
    public String getExtension() {
        return ".pdf";
    }
}

public class HTMLDocument implements Document {
    private StringBuilder content = new StringBuilder();
    
    @Override
    public void open() {
        content.append("<!DOCTYPE html><html><body>");
    }
    
    @Override
    public void addHeader(String header) {
        content.append("<h1>").append(header).append("</h1>");
    }
    
    @Override
    public void addContent(String text) {
        content.append("<p>").append(text).append("</p>");
    }
    
    @Override
    public void save() {
        content.append("</body></html>");
        System.out.println("Saving HTML document...");
    }
    
    @Override
    public String getExtension() {
        return ".html";
    }
}

Now the creator hierarchy:

public abstract class ReportGenerator {
    // Factory method - subclasses must implement
    protected abstract Document createDocument();
    
    // Template method using the factory method
    public final void generateReport(String title, String body) {
        Document doc = createDocument();
        doc.open();
        doc.addHeader(title);
        doc.addContent(body);
        doc.save();
        System.out.println("Report saved as: " + title + doc.getExtension());
    }
}

public class PDFReportGenerator extends ReportGenerator {
    @Override
    protected Document createDocument() {
        return new PDFDocument();
    }
}

public class HTMLReportGenerator extends ReportGenerator {
    @Override
    protected Document createDocument() {
        return new HTMLDocument();
    }
}

Client code becomes clean and extensible:

public class ReportingApplication {
    public static void main(String[] args) {
        ReportGenerator generator = getGeneratorForUser(currentUser);
        generator.generateReport("Q4 Sales", "Revenue increased 15%...");
    }
    
    private static ReportGenerator getGeneratorForUser(User user) {
        return switch (user.getPreferredFormat()) {
            case PDF -> new PDFReportGenerator();
            case HTML -> new HTMLReportGenerator();
            default -> new PDFReportGenerator();
        };
    }
}

Adding Word support requires only two new classes—no modifications to existing code.

Real-World Applications

Factory Method appears throughout framework and library design. Consider a database connection factory:

public interface DatabaseConnection {
    void connect(String connectionString);
    ResultSet executeQuery(String sql);
    void close();
}

public abstract class DatabaseConnectionFactory {
    protected abstract DatabaseConnection createConnection();
    
    public DatabaseConnection getConnection(String connectionString) {
        DatabaseConnection conn = createConnection();
        conn.connect(connectionString);
        return conn;
    }
}

public class PostgreSQLConnectionFactory extends DatabaseConnectionFactory {
    @Override
    protected DatabaseConnection createConnection() {
        return new PostgreSQLConnection();
    }
}

public class MySQLConnectionFactory extends DatabaseConnectionFactory {
    @Override
    protected DatabaseConnection createConnection() {
        return new MySQLConnection();
    }
}

public class SQLiteConnectionFactory extends DatabaseConnectionFactory {
    @Override
    protected DatabaseConnection createConnection() {
        return new SQLiteConnection();
    }
}

This pattern enables plugin architectures where third parties extend your framework without modifying core code. Each database vendor ships their own factory implementation.

Factory Method vs. Abstract Factory vs. Simple Factory

Aspect Simple Factory Factory Method Abstract Factory
Pattern Type Idiom (not GoF) Class pattern Object pattern
Creation Mechanism Static method with conditionals Inheritance Composition
Extensibility Modify factory Add subclass Add factory family
Product Count Single product family Single product Multiple related products
Complexity Low Medium High

Use Simple Factory when you have straightforward creation logic that won’t change often.

Use Factory Method when a class can’t anticipate the objects it needs to create, or when you want subclasses to specify created objects.

Use Abstract Factory when you need to create families of related objects that must be used together.

Benefits and Tradeoffs

Advantages:

  • Open/Closed Principle: Add new products without modifying existing creators
  • Single Responsibility: Creation logic lives in dedicated classes
  • Loose Coupling: Creators work with products through interfaces
  • Testability: Easily mock products by creating test factory subclasses

Drawbacks:

  • Class Proliferation: Each product needs a corresponding creator subclass
  • Complexity Overhead: Overkill for simple creation scenarios
  • Inheritance Rigidity: Deep creator hierarchies become unwieldy

Practical Guidelines

Signs you need Factory Method:

  • You see switch or if-else chains creating different object types
  • Framework code needs extension points for application-specific objects
  • Object creation involves complex configuration that varies by type
  • You’re writing tests and struggling to substitute mock objects

Common mistakes to avoid:

  • Creating factories for objects that never vary
  • Overcomplicating simple scenarios that a constructor handles fine
  • Forgetting that the factory method can have a default implementation

Refactoring from conditionals:

// Before: Conditional creation
public class NotificationService {
    public void send(String message, String channel) {
        Notification notification;
        if (channel.equals("email")) {
            notification = new EmailNotification();
        } else if (channel.equals("sms")) {
            notification = new SMSNotification();
        } else {
            notification = new PushNotification();
        }
        notification.send(message);
    }
}

// After: Factory Method
public abstract class NotificationService {
    protected abstract Notification createNotification();
    
    public void send(String message) {
        Notification notification = createNotification();
        notification.send(message);
    }
}

public class EmailNotificationService extends NotificationService {
    @Override
    protected Notification createNotification() {
        return new EmailNotification();
    }
}

Modern applications often combine Factory Method with dependency injection. The factory becomes an injectable service, giving you both the pattern’s flexibility and DI’s configurability. This hybrid approach delivers extensibility without the inheritance overhead.

Factory Method remains one of the most practical patterns in the catalog. When you find yourself managing object creation complexity, it provides a clean, extensible solution that keeps your codebase maintainable as requirements evolve.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.