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
// 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
// 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.