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.
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)
// 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
// 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);
}
}// 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(); }
}// 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):
// 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 Factory | True Factory Method (GoF) | |
|---|---|---|
| Mechanism | One method, internal switch/if | Abstract method, overridden per subclass |
| Adding a new type | Edit the existing switch | Add a new subclass, edit nothing existing |
| Surrounding workflow | Same for all types (there's only one caller-facing method) | Can vary per ConcreteCreator |
| Polymorphism | None — dispatch is via string/enum comparison | Full 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:
// 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 when | Avoid it when |
|---|---|
| A class can't anticipate which concrete subclass of a product it needs to create — that decision genuinely belongs to a subclass | There 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 step | A 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 product | The 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 product | You'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 Method | Abstract Factory | |
|---|---|---|
| What it creates | One product (possibly of several concrete subtypes, one at a time) | A family of related products that must be used together |
| Mechanism | Inheritance — a subclass overrides one method | Composition — a factory object exposes multiple creation methods, one per product in the family |
| Typical signature | abstract Product createProduct() | interface AbstractFactory { ProductA createProductA(); ProductB createProductB(); } |
| Consistency guarantee | None beyond a single product's type | Guarantees the products returned together are compatible (e.g. all Dark-theme widgets, never a Dark button with a Light checkbox) |
| Growth axis | Adding a new product variant = new Creator subclass | Adding 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-appropriateCalendarsubclass without the caller specifying which.java.nio.file.Files/Pathcreation viaFileSystem— the concretePathimplementation varies by underlying filesystem provider.- Spring's
BeanFactory—getBean()defers to configured providers to decide the concrete implementation returned for an interface type. - JDBC
Connectioncreation —DriverManager.getConnection()returns a driver-specificConnectionimplementation chosen by the driver, not by the calling code. - GUI frameworks — a
Dialogbase class with acreateButton()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.