Vasu Patel logoVasu Patel
← Patterns of Good Design
All chapters in this track
  1. 01Chapter 1: Why Patterns Exist — Vocabulary, Not a Checklist
  2. 02Chapter 2: Patterns and SOLID: How They Relate
  3. 03Chapter 3: Creational Patterns I: Factory Method & Abstract Factory
  4. 04Chapter 4: Creational Patterns II: Builder, Singleton, Prototype
  5. 05Chapter 5: Structural Patterns I: Adapter & Facade
  6. 06Chapter 6: Structural Patterns II: Decorator & Proxy
  7. 07Chapter 7: Structural Patterns III: Composite, Bridge & Flyweight
  8. 08Chapter 8: Behavioral Patterns I: Strategy & Template Method
  9. 09Chapter 9: Behavioral Patterns II: Observer & Mediator
  10. 10Chapter 10: Behavioral Patterns III: Command & Memento
  11. 11Chapter 11: Behavioral Patterns IV: State & Chain of Responsibility
  12. 12Chapter 12: Behavioral Patterns V: Iterator & Visitor
  13. 13Chapter 13: Anti-Patterns and Overuse: Recognizing "Pattern Fever"
  14. 14Chapter 14: How Dynamic and Functional Languages Solve These Problems Differently
  15. 15Chapter 15: Architectural Patterns I: MVC/MVVM & Layered/Hexagonal Architecture
  16. 16Chapter 16: Architectural Patterns II: Event-Driven Architecture & CQRS
  17. 17Chapter 17: Architectural Patterns III: The Repository Pattern
  18. 18Chapter 18: Recognizing Which Pattern Fits an Interview Design Prompt

Chapter 3 of 18

Chapter 3: Creational Patterns I: Factory Method & Abstract Factory

Why this matters

Creational patterns all answer some version of "how should this object get created?" — and the reason that question deserves whole patterns, rather than just calling new, is that construction logic often needs to vary independently of the code that needs the constructed object. Factory Method and Abstract Factory are the two most common answers, and they're frequently confused with each other despite solving related but distinct problems.

Factory Method

The problem it solves: a class needs to create objects, but the exact type to create should be decided by a subclass (or a parameter), not hard-coded into the creating class — so that adding a new type doesn't require modifying the class that creates them.

Before — object creation hard-coded, violating Open/Closed:

class NotificationService {
  send(type: string, message: string): void {
    let notification;
    if (type === "email") notification = { channel: "email", body: message };
    else if (type === "sms") notification = { channel: "sms", body: message };
    // A new notification type means editing this method directly.
    console.log(`Sending via ${notification!.channel}: ${notification!.body}`);
  }
}

After — Factory Method delegates the decision of what to create:

interface Notification {
  send(message: string): void;
}
class EmailNotification implements Notification {
  send(message: string): void { console.log(`Email: ${message}`); }
}
class SmsNotification implements Notification {
  send(message: string): void { console.log(`SMS: ${message}`); }
}

abstract class NotificationCreator {
  abstract createNotification(): Notification; // the "factory method" — subclasses decide what gets created

  notify(message: string): void {
    const notification = this.createNotification();
    notification.send(message);
  }
}

class EmailNotificationCreator extends NotificationCreator {
  createNotification(): Notification { return new EmailNotification(); }
}
class SmsNotificationCreator extends NotificationCreator {
  createNotification(): Notification { return new SmsNotification(); }
}

// Adding a new channel: one new Notification class, one new Creator subclass. Nothing existing changes.
from abc import ABC, abstractmethod

class Notification(ABC):
    @abstractmethod
    def send(self, message: str) -> None: ...

class EmailNotification(Notification):
    def send(self, message: str) -> None:
        print(f"Email: {message}")

class NotificationCreator(ABC):
    @abstractmethod
    def create_notification(self) -> Notification: ...

    def notify(self, message: str) -> None:
        self.create_notification().send(message)

class EmailNotificationCreator(NotificationCreator):
    def create_notification(self) -> Notification:
        return EmailNotification()

When to use it: a base class's algorithm needs to create objects of a type that varies by subclass, and you want new types added by subclassing rather than by editing the base class (this is the OOP book's Open/Closed principle, applied to construction).

When to avoid it: if there's exactly one type being created and no evidence a second is coming, a plain constructor call is simpler and the abstract creator hierarchy is unjustified overhead — the Chapter 1 discipline ("state the problem first") applies directly here.

Complexity cost: an extra class hierarchy (the creators) parallel to the product hierarchy — doubles the number of classes involved for the flexibility gained.

Abstract Factory

The problem it solves: you need to create families of related objects that must be used together, and you want to guarantee that a caller never accidentally mixes objects from different, incompatible families.

Before — no guarantee that related objects stay consistent:

class LightButton { render(): string { return "[Light Button]"; } }
class DarkButton { render(): string { return "[Dark Button]"; } }
class LightCheckbox { render(): string { return "[Light Checkbox]"; } }
class DarkCheckbox { render(): string { return "[Dark Checkbox]"; } }

// Nothing stops this — a caller can accidentally mix themes:
const button = new LightButton();
const checkbox = new DarkCheckbox(); // inconsistent UI, and the compiler has no opinion about it

After — Abstract Factory guarantees a matched family:

interface Button { render(): string; }
interface Checkbox { render(): string; }

interface UiFactory {
  createButton(): Button;
  createCheckbox(): Checkbox;
}

class LightUiFactory implements UiFactory {
  createButton(): Button { return { render: () => "[Light Button]" }; }
  createCheckbox(): Checkbox { return { render: () => "[Light Checkbox]" }; }
}
class DarkUiFactory implements UiFactory {
  createButton(): Button { return { render: () => "[Dark Button]" }; }
  createCheckbox(): Checkbox { return { render: () => "[Dark Checkbox]" }; }
}

function renderForm(factory: UiFactory): void {
  console.log(factory.createButton().render());
  console.log(factory.createCheckbox().render());
  // Structurally impossible to mix Light and Dark here — one factory produces one consistent family.
}

renderForm(new DarkUiFactory());
from typing import Protocol

class Button(Protocol):
    def render(self) -> str: ...

class UiFactory(Protocol):
    def create_button(self) -> Button: ...
    def create_checkbox(self) -> "Checkbox": ...

class DarkUiFactory:
    def create_button(self) -> Button:
        return DarkButton()
    def create_checkbox(self) -> "Checkbox":
        return DarkCheckbox()

When to use it: the system needs to support multiple families of related products (themes, platform-specific UI toolkits, database-vendor-specific query builders) where mixing members of different families would be a bug, and that guarantee needs to be structural, not just a convention.

When to avoid it: if there's only one "family" in practice, or the products aren't actually required to be used together consistently, this is pure ceremony — a plain Factory Method (or no pattern at all) is enough.

Complexity cost: the largest of the creational patterns — one interface per product type, one concrete class per product per family, plus the factory interface and one concrete factory per family. This scales multiplicatively (products × families), so it's justified only when that consistency guarantee is a real, current requirement.

Factory Method vs. Abstract Factory

[SENIOR INSIGHT] The distinction that gets confused most often: Factory Method creates one product, and varies which concrete type by subclassing or a parameter. Abstract Factory creates a whole family of related products, and guarantees they're mutually consistent. A useful rule of thumb: if you find yourself needing several related Factory Methods that must always be called on the same concrete creator to stay consistent (never mixed across creators), that's the exact signal to formalize it into an Abstract Factory instead.

Common mistakes

  1. Reaching for Abstract Factory's full family-of-products machinery when there's only ever one family, or when the "families" don't actually need to stay consistent with each other — Factory Method (or nothing) is enough.
  2. Building a Factory Method hierarchy for a single, unlikely-to-change type, adding an abstract creator class for no real flexibility gained — Chapter 1's premature-pattern warning, concretely.
  3. Letting Abstract Factory family boundaries leak — e.g., code somewhere constructing a DarkButton directly instead of going through the factory, silently reintroducing the exact inconsistency the pattern exists to prevent.
  4. Confusing the two patterns' names in conversation, which causes real miscommunication in design discussions — precision matters here specifically because the two are so easily conflated.

Checklist: choosing a creational pattern for object construction

  1. Ask: does only the concrete type of a single object need to vary? → Factory Method.
  2. Ask: do multiple related objects need to vary together, with a consistency guarantee? → Abstract Factory.
  3. Check whether a second variant genuinely exists or is imminent before building either hierarchy — a single implementation doesn't justify the ceremony.
  4. For Abstract Factory, audit that nothing in the codebase constructs family members directly, bypassing the factory — that would defeat the entire guarantee.

Discussion / interview questions

  1. "What's the difference between Factory Method and Abstract Factory?" Strong answer: Factory Method creates one product, with the concrete type varying by subclass/parameter; Abstract Factory creates a family of related products and guarantees they stay consistent with each other. States the rule of thumb: several Factory Methods that must always agree on the same concrete creator is the signal to formalize as Abstract Factory.

  2. "When would introducing a Factory Method be premature?" Strong answer: When there's only one concrete type currently, with no real evidence a second is coming — a plain constructor call is simpler, and the abstract creator hierarchy is unjustified overhead (ties to Chapter 1's discipline).

  3. "What real problem does Abstract Factory's 'family consistency' guarantee actually prevent?" Strong answer: Prevents a caller from accidentally combining incompatible objects (e.g., a light-theme button with a dark-theme checkbox) by making it structurally impossible — one factory instance produces one consistent set, rather than relying on developer discipline.

  4. "What's the complexity cost of Abstract Factory, and why does it scale the way it does?" Strong answer: Cost scales multiplicatively with products × families (one interface per product, one class per product per family, plus factories) — correctly identifies this as the reason to reserve it for genuine multi-family consistency needs.

Chapter summary / cheat sheet

  • Factory Method: one product, concrete type varies by subclass/parameter — the Open/Closed fix applied to object creation.
  • Abstract Factory: a family of related products created together, with a structural guarantee against mixing incompatible members.
  • Rule of thumb: several Factory Methods that must always be called together consistently is the signal to formalize into an Abstract Factory.
  • Both require a real, current need for variation (a real second type, a real family-consistency requirement) — otherwise a plain constructor is simpler and the pattern is premature.