Factory Method

Intermediate · 11 min read · ▶ live playground · ✦ checkpoint

Finance asks for HTML receipts. The rendering code is ready in an hour, but shipping it still means reopening the creator's switch, adding another type tag, and retesting the PDF and CSV branches that already worked.

The Factory Method pattern defines a creation method in a base creator and lets subclasses override that method to choose the concrete product. The creator's stable workflow uses the product interface, so a new product arrives with a new creator subclass instead of another branch in that workflow.

The trade-off is structural: Factory Method removes a growing conditional but adds an inheritance hierarchy and usually one creator subclass per product variation.

Before: the creator lists every product

A product is the object being created. PDF and CSV receipts already share a Product contract, yet Creator still knows both concrete class names and selects between them.

class diagram

Before: one creator switches over every product

Before: one creator switches over every product
  1. 1Product gives the workflow one rendering operation, independent of file format.
  2. 2PdfProduct fulfills the shared rendering contract.
  3. 3CsvProduct fulfills the same contract with a different representation.
  4. 4Creator names PdfProduct inside its type-tag branch.
  5. 5The same method names CsvProduct, so every new format reopens this class.

The interface helps code after construction, but it does not fix the creation branch:

interface Product {
  render(orderId: string): string;
}
 
class PdfProduct implements Product {
  public render(orderId: string): string {
    return "PDF receipt for " + orderId;
  }
}
 
class CsvProduct implements Product {
  public render(orderId: string): string {
    return "CSV receipt for " + orderId;
  }
}
 
type ProductKind = "pdf" | "csv";
 
class Creator {
  // smell: each product addition edits this stable list.
  public createProduct(kind: ProductKind): Product {
    switch (kind) {
      case "pdf":
        return new PdfProduct();
      case "csv":
        return new CsvProduct();
    }
  }
 
  public operation(kind: ProductKind, orderId: string): string {
    const product = this.createProduct(kind);
    return "Prepared " + product.render(orderId);
  }
}
 
console.log(new Creator().operation("pdf", "ORD-7"));

Prepared PDF receipt for ORD-7

operation() is stable: acquire a product, render the receipt, and mark it prepared. The varying choice sits inside the same class, however. Adding HtmlProduct changes the union, the switch, and the tests for Creator.

This is the same Open/Closed pressure you saw in shipping policies. The variation is now object construction rather than a price formula.

After: subclasses own the creation choice

A creator is the class whose workflow needs a product. Its createProduct() method is the factory method, the overridable operation that moves concrete construction into subclasses.

class diagram

After: each creator subclass chooses one product

After: each creator subclass chooses one product
  1. 1Product is the stable result the creator workflow knows how to use.
  2. 2PdfProduct implements that result for PDF output.
  3. 3CsvProduct implements the same result for CSV output.
  4. 4Abstract Creator runs the workflow against Product and leaves construction open.
  5. 5PdfCreator inherits the workflow and overrides only the factory method.
  6. 6Its override chooses PdfProduct without adding a format branch to Creator.
  7. 7CsvCreator inherits the same stable operation.
  8. 8Its factory method chooses CsvProduct; polymorphism selects the creator at runtime.

The abstract creator does not know which concrete product will come back. It only knows that the result honors Product:

interface Product {
  render(orderId: string): string;
}
 
class PdfProduct implements Product {
  public render(orderId: string): string {
    return "PDF receipt for " + orderId;
  }
}
 
class CsvProduct implements Product {
  public render(orderId: string): string {
    return "CSV receipt for " + orderId;
  }
}
 
abstract class Creator {
  public abstract createProduct(): Product;
 
  public operation(orderId: string): string {
    const product = this.createProduct();
    return "Prepared " + product.render(orderId);
  }
}
 
class PdfCreator extends Creator {
  public createProduct(): Product {
    return new PdfProduct();
  }
}
 
class CsvCreator extends Creator {
  public createProduct(): Product {
    return new CsvProduct();
  }
}
 
const creator: Creator = new PdfCreator();
console.log(creator.operation("ORD-7"));

Prepared PDF receipt for ORD-7

The caller chooses a creator once. The inherited operation() then calls the overridden method through polymorphism. Adding HTML means adding HtmlProduct and HtmlCreator; the base Creator and its workflow remain unchanged.

The pattern does not erase selection. Application setup still decides whether to construct PdfCreator or CsvCreator, perhaps from configuration. The branch moves to a composition boundary and runs once, instead of spreading through the workflow that uses the product.

Factory Method, not a simple factory

A simple factory is a function or object that centralizes construction, often with a conditional such as createProduct(kind). It can be an excellent small design, but subclasses do not override its choice.

Factory Method uses inheritance: a creator subclass overrides one method, and the base creator continues a workflow with the returned product. It is strongest when subclasses already represent meaningful creator variations or when the workflow should be reused unchanged.

Abstract Factory, the next pattern, addresses a different scale. Factory Method chooses one product through one overridable method. Abstract Factory exposes several creation methods that produce a matching family of related products.

When NOT to use it

If there is exactly one product and no credible variation, call its constructor. If selection is a small rule with no reusable creator workflow, a factory function is easier to follow:

interface Product {
  render(orderId: string): string;
}
 
class PdfProduct implements Product {
  public render(orderId: string): string {
    return "PDF receipt for " + orderId;
  }
}
 
class CsvProduct implements Product {
  public render(orderId: string): string {
    return "CSV receipt for " + orderId;
  }
}
 
function createProduct(kind: "pdf" | "csv"): Product {
  return kind === "pdf" ? new PdfProduct() : new CsvProduct();
}

That function still centralizes creation and may be all the design needs. Adding abstract and concrete creator classes before variation appears violates YAGNI and makes navigation harder.

Factory Method also fits poorly when the creator hierarchy exists only to dodge a two-case conditional. Its cost is more types, inheritance coupling, and a concrete creator that must be selected somewhere. Pay that cost when the base workflow is valuable and product creation is the part subclasses genuinely need to vary.

Checkpoint

Answer all three to mark this lesson complete

Factory Method varies one product inside a creator workflow. Next, Abstract Factory keeps several related products in the same family.

+50 XP on completion