Creational Pattern (GoF)

Factory Method

Defines an interface for creating an object, but lets subclasses (or implementations) decide which concrete class to instantiate — decoupling the code that uses the object from the code that creates it.

Intent

Define an interface for creating an object, delegating to subclasses (or concrete implementations) the decision of which class to instantiate. Factory Method lets a class defer instantiation to its subclasses, promoting the Open/Closed Principle: to support a new product type, you just create a new subclass — without modifying existing code.

Cataloged by Gamma, Helm, Johnson and Vlissides in the book Design Patterns: Elements of Reusable Object-Oriented Software (1994), Factory Method belongs to the creational patterns category. It's one of the most widely used patterns in practice and serves as the foundation for Abstract Factory.

Problem

Imagine a notification system that, initially, only sent emails. As the product grew, the need arose to send SMS and, later, push notifications. The naive solution — an if/else or switch inside the business code choosing which notification class to instantiate — violates two fundamental principles:

  • Open/Closed: every new channel requires modifying already existing and tested code, risking regressions.
  • Single Responsibility: the business class accumulates the creation decision together with the usage logic.

The problem gets worse when instantiation involves complex configuration (e.g. SMTP authentication, API credentials, connection pooling). Spreading this logic across the code turns any provider change into a hunt across multiple files.

Simple Factory vs Factory Method

It's worth distinguishing two concepts that are frequently confused:

  • Simple Factory (not a GoF pattern): a static class or function with a switch that returns different instances. It's simple and sufficient when the types are fixed and rarely change. The problem: it still violates Open/Closed when new types appear.
  • Factory Method (GoF pattern): defines a creation method in a base class (abstract or concrete) and lets subclasses override that method to provide the concrete product. Extensible without modifying the base code.

Use Simple Factory when the types are few and stable; prefer Factory Method when type variation is the central extension point of your design.

Solution

Factory Method organizes the code into four participants:

  1. Product (interface/abstract class): defines the contract that every concrete product must fulfill. E.g.: Notification interface with the method send(recipient, message).
  2. ConcreteProduct: implements the Product's contract. E.g.: EmailNotification, SmsNotification, PushNotification.
  3. Creator (base class): declares the factory method — createNotification() — and may contain business logic that uses the product returned by the method. The Creator doesn't know which ConcreteProduct will be created.
  4. ConcreteCreator: overrides the factory method to instantiate and return a specific ConcreteProduct.

The client code works with Creator and Product through interfaces — never referencing the concrete classes directly. To add a new notification channel, you just create a new ConcreteProduct + ConcreteCreator pair.

Structure

Simplified UML diagram using the notification example:

          «interface»
         Notification
    ┌───────────────────┐
    │ + send(dest, msg) │
    └───────────────────┘
              ▲
    ┌─────────┴──────────────────────┐
    │                                │
EmailNotification         SmsNotification
(ConcreteProduct)         (ConcreteProduct)


          «abstract»
      NotificationCreator
    ┌──────────────────────────────────────┐
    │ # createNotification(): Notification │  ← factory method (abstract)
    │ + notify(dest, msg): void            │  ← uses the product via interface
    └──────────────────────────────────────┘
              ▲
    ┌─────────┴────────────────────────┐
    │                                  │
EmailCreator                    SmsCreator
(ConcreteCreator)               (ConcreteCreator)
createNotification()            createNotification()
  → new EmailNotification()       → new SmsNotification()


Call flow:

  client
    │
    │  emailCreator.notify("a@b.com", "Hello")
    ▼
  NotificationCreator.notify()
    │
    │  notification = this.createNotification()  ← calls the factory method
    │                                       (polymorphism resolves to EmailNotification)
    │  notification.send("a@b.com", "Hello")
    ▼
  EmailNotification.send()

Code examples

Example 1 — Notification system

The Creator defines the contract and the business logic; each ConcreteCreator provides its product. The client code only knows the base class.

// ── Product ──────────────────────────────────────────────────
interface Notification {
  send(recipient: string, message: string): void;
}

// ── ConcreteProducts ─────────────────────────────────────────
class EmailNotification implements Notification {
  send(recipient: string, message: string): void {
    console.log(`[EMAIL] To: ${recipient} | Message: ${message}`);
  }
}

class SmsNotification implements Notification {
  send(recipient: string, message: string): void {
    console.log(`[SMS] To: ${recipient} | Message: ${message}`);
  }
}

class PushNotification implements Notification {
  send(recipient: string, message: string): void {
    console.log(`[PUSH] To: ${recipient} | Message: ${message}`);
  }
}

// ── Creator ──────────────────────────────────────────────────
// The factory method is abstract: subclasses are required to provide it.
abstract class NotificationCreator {
  // Factory Method — each subclass decides which product to create.
  protected abstract createNotification(): Notification;

  // Business logic reused by every creator.
  // Uses the product via interface — without knowing the concrete class.
  public notify(recipient: string, message: string): void {
    const notification = this.createNotification();
    notification.send(recipient, message);
  }
}

// ── ConcreteCreators ─────────────────────────────────────────
class EmailCreator extends NotificationCreator {
  protected createNotification(): Notification {
    return new EmailNotification();
  }
}

class SmsCreator extends NotificationCreator {
  protected createNotification(): Notification {
    return new SmsNotification();
  }
}

class PushCreator extends NotificationCreator {
  protected createNotification(): Notification {
    return new PushNotification();
  }
}

// ── Client code ───────────────────────────────────────────────
// The client works with NotificationCreator — without coupling itself
// to concrete product classes.
function sendAlert(creator: NotificationCreator, msg: string): void {
  creator.notify("user@example.com", msg);
}

sendAlert(new EmailCreator(), "Your invoice is available.");
sendAlert(new SmsCreator(),   "Verification code: 4821");
sendAlert(new PushCreator(),  "New comment on your post.");

Example 2 — Parameterized Factory Method (subclass-free variant)

In languages with support for first-class functions (TypeScript) or closures, the factory method can be parameterized — avoiding a subclass explosion for simple cases. This variant is less orthodox, but very common in practice:

type Channel = "email" | "sms" | "push";

// Factory registry: each channel maps to a creator function.
// Adding a new channel = registering a new entry in the map.
const factories: Record<Channel, () => Notification> = {
  email: () => new EmailNotification(),
  sms:   () => new SmsNotification(),
  push:  () => new PushNotification(),
};

function createNotification(channel: Channel): Notification {
  const factory = factories[channel];
  return factory();
}

// ── Usage ────────────────────────────────────────────────────
const channels: Channel[] = ["email", "sms", "push"];

for (const channel of channels) {
  const notif = createNotification(channel);
  notif.send("user@example.com", `Alert via ${channel}`);
}

When to use

  • The type of object to create isn't known at compile time: it depends on configuration, the environment, or user input — and new variations will appear in the future.
  • You want the client code to be independent of the concrete classes it creates — decoupling "usage" from "creation".
  • You want to provide extension points: libraries and frameworks use Factory Method to let the library's consumer replace internal components without changing the framework's source code.
  • Creation involves reusable logic: if the business logic that uses the product (the Creator's notify method) is identical for every product, only varying the instantiation, Factory Method eliminates duplication via inheritance or composition.

When to avoid

  • When a Simple Factory solves it: if the product types are few, stable and known at compile time, a switch in a factory function is simpler and equally readable — without the ceremony of subclasses.
  • When there's no real variation: if there's only one concrete product type now and for the foreseeable future, the abstraction is premature.
  • In projects with DI containers: NestJS, Spring, Laravel and similar frameworks already solve implementation selection through dependency injection and configuration — manually reimplementing Factory Method is usually redundant.

Pros and cons

Pros

  • Follows the Open/Closed Principle — new product types don't require modifying existing code.
  • Decouples the client code from the concrete product classes.
  • Centralizes creation logic, eliminating instantiations scattered across the code.
  • Makes testing easier: the Creator can be tested with a fake product injected via a test subclass.
  • Lets subclasses reuse the Creator's business logic without repetition.

Cons

  • Introduces a class hierarchy: for each new ConcreteProduct, a new ConcreteCreator is generally required — can lead to a subclass explosion.
  • More complex than a Simple Factory for cases where the types are fixed and few.
  • Can be hard to understand for those unfamiliar with the pattern, since the creation flow isn't immediately obvious when reading the client code.

Common pitfalls

1. Subclass explosion

If every product variation requires a new Creator + Product pair, and the application has dozens of variations, the hierarchy grows rapidly. When the creators have no business logic of their own (they're empty besides the factory method), consider replacing the hierarchy with a factory map (as in Example 2) or with an Abstract Factory if the products are related in families.

2. Confusing Simple Factory with Factory Method

Warning: Simple Factory is a programming idiom, not a GoF pattern. A static method Notification::create("email") that does a switch($type) solves many practical problems without the ceremony of subclasses. Apply Factory Method only when extension via subclassing is the actual goal — otherwise you add complexity without proportional benefit.

3. Factory Method vs Abstract Factory

Factory Method creates a single product — the variation is in which concrete class of that product gets instantiated. Abstract Factory creates families of related products — several objects that must work together. If you find yourself creating several related factory methods in the same Creator, your design probably calls for an Abstract Factory.

4. Selection logic leaking into the client

A common mistake: the client code chooses which ConcreteCreator to instantiate with an if/switch based on configuration. This defeats the purpose of the pattern. Choosing the concrete creator should happen as close as possible to the application's edge (entry point, configuration, DI container), not scattered throughout the business logic.

Related patterns

Factory Method is the foundation of several other patterns and frequently appears together with them:

Singleton is frequently implemented alongside the Creator when the factory itself needs to be unique in the application. Abstract Factory can be seen as a group of coordinated Factory Methods for creating families of compatible products — while Factory Method focuses on a single product type, Abstract Factory coordinates the creation of multiple types that must work together. Template Method shares the same inheritance structure: the base class defines the algorithm's skeleton with abstract steps, and subclasses provide the concrete implementations — the factory method is, essentially, a Template Method specialized in object creation.