02-creational-patterns

Factory Method Pattern: Deferring Instantiation to Subclasses

How Factory Method decouples client code from concrete classes by letting subclasses decide what to instantiate — with parameterized factories, Template Method integration, and the line where factory proliferation becomes a smell.

August 11, 2026
lldcreationalfactorydecouplingtemplate-method

Factory Method Pattern

Factory Method defines an interface for creating an object, but lets subclasses decide which concrete class to instantiate. The parent class works entirely against the abstract product type; it never needs to know — and never hardcodes — the concrete class being built. This is the pattern that gets Open/Closed applied to object creation itself: adding a new product variant means adding a new subclass, not editing an existing factory method's conditional logic.


1. Intent

Client code frequently needs to create an object without knowing its exact concrete type in advance — the type depends on configuration, environment, or a subclass's specialization. Directly calling new ConcreteClass() inside shared logic hardcodes that decision and breaks the moment a new variant is needed. Factory Method's intent is to push the "which concrete class" decision down into a subclass that overrides a single creation method, while all the surrounding logic in the parent class stays untouched and works against the abstract product type only.


2. The Naive Approach (Violation)

java
// VIOLATION: NotificationService directly instantiates concrete notification
// types via an if/else chain keyed off a string/enum.
class NotificationService {
    void notifyUser(String channel, String userId, String message) {
        Notification notification;
        if (channel.equals("EMAIL")) {
            notification = new EmailNotification();
        } else if (channel.equals("SMS")) {
            notification = new SmsNotification();
        } else if (channel.equals("PUSH")) {
            notification = new PushNotification();
        } else {
            throw new IllegalArgumentException("Unknown channel: " + channel);
        }
        notification.send(userId, message);
    }
}

Every new channel — Slack, WhatsApp, in-app banner — means editing this already-tested method again, exactly the Open/Closed violation covered in the SOLID guide. Worse, this logic tends to get copy-pasted anywhere notifications are created, so a new channel requires hunting down every duplicate if/else chain across the codebase.


3. Structure

The Creator declares the factory method (abstract or with a default) and typically also contains business logic that uses the created product — that combination is what makes Factory Method different from a bare object-creation utility (see Section 6). Each ConcreteCreator overrides the factory method to produce one specific ConcreteProduct.


4. Java Implementation

java
// Product hierarchy
interface Notification {
    void send(String userId, String message);
}
 
class EmailNotification implements Notification {
    public void send(String userId, String message) {
        System.out.println("Emailing " + userId + ": " + message);
    }
}
 
class SmsNotification implements Notification {
    public void send(String userId, String message) {
        System.out.println("Texting " + userId + ": " + message);
    }
}
 
class PushNotification implements Notification {
    public void send(String userId, String message) {
        System.out.println("Push to " + userId + ": " + message);
    }
}
java
// Creator: declares the factory method AND drives the shared workflow
// around it — this is the key structural piece, not just a switch statement
// wrapped in a class.
abstract class NotificationCreator {
    // The factory method — subclasses decide what gets built.
    protected abstract Notification createNotification();
 
    // Shared logic that uses the product, unaware of its concrete type.
    // Adding a new channel NEVER requires touching this method.
    public final void notifyUser(String userId, String message) {
        Notification notification = createNotification();
        logAttempt(userId);
        notification.send(userId, message);
    }
 
    private void logAttempt(String userId) {
        System.out.println("Dispatching notification to " + userId);
    }
}
 
class EmailNotificationCreator extends NotificationCreator {
    protected Notification createNotification() { return new EmailNotification(); }
}
 
class SmsNotificationCreator extends NotificationCreator {
    protected Notification createNotification() { return new SmsNotification(); }
}
 
class PushNotificationCreator extends NotificationCreator {
    protected Notification createNotification() { return new PushNotification(); }
}
java
// FIXED: adding WhatsApp support means ADDING two classes, editing zero existing ones
class WhatsAppNotification implements Notification {
    public void send(String userId, String message) {
        System.out.println("WhatsApp to " + userId + ": " + message);
    }
}
class WhatsAppNotificationCreator extends NotificationCreator {
    protected Notification createNotification() { return new WhatsAppNotification(); }
}
 
// Usage:
NotificationCreator creator = new EmailNotificationCreator();
creator.notifyUser("user-42", "Your order has shipped.");

Notice notifyUser() is final and contains real shared logic (logging, then sending) — this is deliberate. If NotificationCreator had no shared behavior around the factory method, you wouldn't need Factory Method at all; a Simple Factory function would do. Factory Method earns its keep specifically when the creation varies but the surrounding workflow doesn't.


5. Parameterized Factory vs. True Factory Method

A common simplification — often what people mean when they casually say "factory" — is a single static method with an internal switch, sometimes called a Simple Factory (not a GoF pattern on its own, but ubiquitous):

java
// Parameterized / Simple Factory — a switch replaces the if/else chain,
// but there is still ONE place that knows about every concrete type.
class NotificationFactory {
    static Notification create(String channel) {
        return switch (channel) {
            case "EMAIL" -> new EmailNotification();
            case "SMS" -> new SmsNotification();
            case "PUSH" -> new PushNotification();
            default -> throw new IllegalArgumentException("Unknown channel: " + channel);
        };
    }
}

This is not the GoF Factory Method — there's no subclassing, no polymorphic dispatch, and adding a channel still means editing this method. It's a perfectly reasonable, simpler choice when:

  • The set of product types is centrally known and doesn't need per-subclass specialization of the surrounding workflow.
  • You don't need NotificationCreator-style shared behavior wrapped around creation.

True Factory Method is the right call when different creators also need different surrounding behavior — i.e., you need polymorphism on the workflow, not just on the object being built. If you find yourself writing a Simple Factory switch and also wanting different logging/validation/retry behavior per channel, that's the signal to promote it to full Factory Method with subclassed Creators.

Simple/Parameterized FactoryTrue Factory Method (GoF)
MechanismOne method, internal switch/ifAbstract method, overridden per subclass
Adding a new typeEdit the existing switchAdd a new subclass, edit nothing existing
Surrounding workflowSame for all types (there's only one caller-facing method)Can vary per ConcreteCreator
PolymorphismNone — dispatch is via string/enum comparisonFull virtual dispatch

6. Combining Factory Method with Template Method

Factory Method is frequently a component of Template Method rather than standing alone — the notifyUser() method above is itself a small Template Method: it defines a fixed algorithm skeleton (log, then send) with one customizable step (createNotification()). This combination is extremely common in frameworks:

java
// A document-processing pipeline: the ALGORITHM is fixed (Template Method),
// but the PARSER used within it varies by subclass (Factory Method).
abstract class DocumentProcessor {
    // Template Method: fixed algorithm skeleton, final so subclasses can't reorder it
    public final void process(byte[] rawDocument) {
        Parser parser = createParser();      // factory method — the customizable step
        Document doc = parser.parse(rawDocument);
        validate(doc);
        persist(doc);
    }
 
    protected abstract Parser createParser(); // subclasses decide: PdfParser, CsvParser, ...
 
    private void validate(Document doc) { /* shared validation */ }
    private void persist(Document doc) { /* shared persistence */ }
}
 
class PdfDocumentProcessor extends DocumentProcessor {
    protected Parser createParser() { return new PdfParser(); }
}
class CsvDocumentProcessor extends DocumentProcessor {
    protected Parser createParser() { return new CsvParser(); }
}

This is a common real-world shape: frameworks (Spring's AbstractApplicationContext, JDBC drivers) define a fixed lifecycle and expose one or more factory-method "hook" points for subclasses to plug in the concrete pieces.


7. When to Use vs. When It's Overkill

Use Factory Method whenAvoid it when
A class can't anticipate which concrete subclass of a product it needs to create — that decision genuinely belongs to a subclassThere is exactly one product type and no realistic prospect of more — you're adding indirection for a variation that doesn't exist
You have shared workflow logic (Template Method) that needs one customizable creation stepA Simple Factory static method already fully solves the problem because the workflow around creation never varies
Different creators need to bundle different validation/logging/setup around the same kind of productThe set of types is small, fixed, and unlikely to grow (avoid factory proliferation — see below)
You're building a framework/library where consumers subclass to plug in their own productYou're building application code where a simple new X() or a config-driven map lookup is clearer
⚠️

Avoiding factory proliferation: don't create a dedicated Creator subclass hierarchy for a single concrete product, or for products that will only ever have one implementation. A factory whose only job is to return new TheOneAndOnlyThing() is pure ceremony. Factory Method pays for itself only when there are (or will realistically be) multiple products and varying surrounding behavior per product.


8. Factory Method vs. Abstract Factory

This is the distinction interviewers ask for most directly, and the two are often confused because both hide new behind a method:

Factory MethodAbstract Factory
What it createsOne product (possibly of several concrete subtypes, one at a time)A family of related products that must be used together
MechanismInheritance — a subclass overrides one methodComposition — a factory object exposes multiple creation methods, one per product in the family
Typical signatureabstract Product createProduct()interface AbstractFactory { ProductA createProductA(); ProductB createProductB(); }
Consistency guaranteeNone beyond a single product's typeGuarantees the products returned together are compatible (e.g. all Dark-theme widgets, never a Dark button with a Light checkbox)
Growth axisAdding a new product variant = new Creator subclassAdding a new family = new ConcreteFactory; adding a new product kind to every family = editing the AbstractFactory interface

In short: Factory Method answers "which one class do I build here?" — Abstract Factory answers "which entire matching set of classes do I build?" See the Abstract Factory Pattern guide for the full family-of-products treatment, including why Abstract Factory is frequently implemented internally using multiple Factory Methods.


9. Real-World / Production Examples

  • java.util.Calendar.getInstance() — returns a locale/timezone-appropriate Calendar subclass without the caller specifying which.
  • java.nio.file.Files / Path creation via FileSystem — the concrete Path implementation varies by underlying filesystem provider.
  • Spring's BeanFactorygetBean() defers to configured providers to decide the concrete implementation returned for an interface type.
  • JDBC Connection creationDriverManager.getConnection() returns a driver-specific Connection implementation chosen by the driver, not by the calling code.
  • GUI frameworks — a Dialog base class with a createButton() factory method overridden per platform-specific dialog subclass.

Interview Questions

  • What problem does Factory Method solve that a plain new ConcreteClass() call doesn't?
  • What's the structural difference between a "Simple Factory" (static method with a switch) and the GoF Factory Method pattern?
  • Why is Factory Method often found paired with Template Method? Walk through an example.
  • How does Factory Method satisfy the Open/Closed Principle specifically at the point of object creation?
  • What's "factory proliferation," and how would you recognize you've over-applied this pattern in a code review?
  • Explain the difference between Factory Method and Abstract Factory using the "one product vs. family of products" framing.
  • Give a real framework example (Java standard library or Spring) that uses Factory Method, and identify the Creator and Product roles in it.