Creational Pattern Pattern Deep Dive

Factory Method

Sooner or later, most code needs to create an object whose exact type isn't known until the program is running — which subclass to build depends on a config value, a user choice, or an incoming request. The Factory Method pattern answers a focused question: how do you let subclasses decide which concrete class to instantiate, without the calling code ever writing a concrete constructor itself?

⚠️ The Problem

Imagine a notification system that started out only sending email. The code is full of lines like new EmailNotification(message), scattered across dozens of call sites — a signup handler, a password-reset flow, an admin alert job. Now the product adds SMS notifications. Every one of those call sites needs to grow an if (channel === "sms") branch, and if a future channel (push notifications, Slack webhooks) gets added, every call site needs editing again. The real problem isn't "how do we construct an SMS notification" — it's that knowledge of which concrete class to build is duplicated everywhere the object is needed, so every new type means hunting down and editing every single creation site.

✅ The Solution

Define a creator with a factory method — a method whose only job is to produce an object of some product type. Instead of calling new ConcreteClassX() directly, client code calls the factory method. A base creator can implement shared logic around the object once it exists, while each subclass overrides the factory method to return its own concrete type. Adding a new type means adding one new creator subclass — nothing that already works has to change.

Example 1 — a multi-channel notification system

Primary Example — JavaScript
// Product interface
class Notification {
  send(message) {
    throw new Error("send() must be implemented");
  }
}

class EmailNotification extends Notification {
  send(message) {
    console.log(`Sending EMAIL: ${message}`);
  }
}

class SMSNotification extends Notification {
  send(message) {
    console.log(`Sending SMS: ${message}`);
  }
}

// Creator base class — declares the factory method
class NotifierCreator {
  // Factory method: subclasses decide the concrete product
  createNotification() {
    throw new Error("createNotification() must be implemented");
  }

  // Shared logic that uses the product, without knowing its concrete type
  notify(message) {
    const notification = this.createNotification();
    notification.send(message);
  }
}

class EmailNotifierCreator extends NotifierCreator {
  createNotification() {
    return new EmailNotification();
  }
}

class SMSNotifierCreator extends NotifierCreator {
  createNotification() {
    return new SMSNotification();
  }
}

function alertUser(creator, message) {
  creator.notify(message); // never touches a concrete class directly
}

alertUser(new EmailNotifierCreator(), "Your order shipped");
alertUser(new SMSNotifierCreator(), "Your OTP is 4821");

alertUser() never writes new EmailNotification() or new SMSNotification() — it only calls notify() on whichever creator it was handed. Adding a push-notification channel later means writing one new PushNotifierCreator class; alertUser() and every other call site stay untouched.

Example 2 — a secondary context: document exporters

Secondary Example — Java
// Product interface
interface Exporter {
    void export(String content);
}

class PDFExporter implements Exporter {
    public void export(String content) {
        System.out.println("Exporting as PDF: " + content);
    }
}

class CSVExporter implements Exporter {
    public void export(String content) {
        System.out.println("Exporting as CSV: " + content);
    }
}

// Creator base class
abstract class ExporterCreator {
    // Factory method — overridden per format
    protected abstract Exporter createExporter();

    public void exportReport(String content) {
        Exporter exporter = createExporter();
        exporter.export(content);
    }
}

class PDFExporterCreator extends ExporterCreator {
    protected Exporter createExporter() {
        return new PDFExporter();
    }
}

class CSVExporterCreator extends ExporterCreator {
    protected Exporter createExporter() {
        return new CSVExporter();
    }
}

public class ReportApp {
    public static void main(String[] args) {
        ExporterCreator creator = new PDFExporterCreator();
        creator.exportReport("Q3 sales figures");

        creator = new CSVExporterCreator();
        creator.exportReport("Q3 sales figures");
    }
}

Same shape, different domain: ReportApp depends only on the abstract ExporterCreator type. Whether the report ends up as a PDF or a CSV is entirely decided by which concrete creator subclass got instantiated — a new JSONExporterCreator could be dropped in tomorrow without touching ReportApp at all.

👍 When To Use It

  • A class can't anticipate the exact type of objects it must create — the decision belongs to a subclass or configuration, not to the base class itself.
  • You want to give subclasses a hook to extend which objects get created, without duplicating the surrounding logic that uses those objects.
  • You're building a framework or library and want to let consumers plug in their own product types by subclassing a creator, rather than editing the framework's internals.

👎 When NOT To Use It

  • When there's only ever going to be one product type — a plain constructor call is simpler and the extra creator/product hierarchy is pure overhead.
  • When you need to create a whole family of related objects that must stay consistent with each other — that's a job for Abstract Factory, not a single Factory Method.
  • When a simple parameter-based branch (a lookup table or switch mapping a string to a constructor) would be just as clear and you don't need subclassing at all — Factory Method earns its complexity when subclasses genuinely vary behavior, not just which class name gets called.

❓ FAQ

Isn't this just a fancy if/else that picks a class?

A simple factory function with an if/else or switch is a related, lighter-weight idea — sometimes called a "simple factory," and it's a perfectly fine tool. Factory Method specifically means the choice of concrete product is made by overriding a method in a subclass, so the creator hierarchy itself expresses the variation, and new types are added by adding new subclasses rather than editing a branch inside existing code.

Does the factory method have to be abstract?

No — it can have a sensible default implementation that subclasses are free to override, rather than being forced to implement it. That's a common variant: the base creator ships a reasonable default product, and only specialized creators need to override createNotification()/createExporter() at all.

How is this different from just calling a static factory method like Notification.create("email")?

A single static method that branches on a string is the "simple factory" mentioned above — convenient, but every new type still means editing that one method. The GoF Factory Method pattern instead relies on polymorphism: each creator subclass owns its own creation logic, so extending the system means adding a class, not editing one.

🙋 There Are No Dumb Questions

Q: Doesn't this mean I end up with twice as many classes — one creator and one product per type?
A: Yes, and that's the deliberate tradeoff. You're trading a small amount of upfront structure for the ability to add new types without touching existing, already-tested code. For a system with two types that will never grow, that tradeoff isn't worth it — plain constructors are fine. For a system where new channels/formats/types show up regularly, it pays for itself quickly.

✅ Quick Check

What does Factory Method let you do that hardcoding new EmailNotification() everywhere does not?


Next up: what if you need to create not just one object, but a whole family of related objects that must all match each other — like a UI toolkit's buttons and checkboxes that need to agree on a light or dark theme? That's Abstract Factory.

← Back to
Next →