Design Patterns Tutorial 0/70 lessons ~6 min read Lesson 18

    Factory Method

    Factory Method moves the decision of which concrete class to create out of the consumer and into a dedicated method.

    Course progress0%
    Focus
    21 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    Introduction

    Factory Method moves the decision of which concrete class to create out of the consumer and into a dedicated method. The consumer asks for a PaymentGateway; a subclass or registry returns StripeGateway, AdyenGateway, or a test double. Every framework extension point — Spring @Bean, JUnit test runners, Angular component factories — is Factory Method waiting for an override.

    The story

    A logistics company added its third carrier integration and found three switch statements scattered across the codebase. A Factory Method on the CarrierGateway hierarchy collapsed them into one creation point — a fourth carrier became one new class plus a registry line.

    The business problem

    Scattered construction logic violates Open/Closed and slows variant growth:

    • Switch sprawl: same if/else chain copied to controllers, jobs, and tests.
    • OCP violations: new variant means editing every construction site.
    • Duplicated wiring: auth, config, validation repeated at each call site.
    • Untestable consumers: hard-coded new Concrete() — no seam for fakes.

    The problem teams faced

    Before Factory Method, these construction smells appear:

    • Multiple switch chains decide which subclass to instantiate.
    • Adding a variant requires editing stable modules — regression risk.
    • Construction setup duplicated — drift between call sites.
    • Tests monkey-patch constructors instead of injecting products.

    Understanding the topic

    Intent: Define an interface for creating an object; let subclasses or registries decide which class to instantiate.

    • Product — interface the factory returns (PaymentGateway).
    • ConcreteProduct — StripeGateway, AdyenGateway.
    • Creator — declares factoryMethod(); may include template behaviour using the product.
    • Registry form (TS) — map key → factory fn; self-register at import time.

    Internal architecture

    Registry-based Factory Method (TypeScript):

    ts
    interface PaymentGateway { charge(amount: Money): Promise<Receipt> }
    const registry = new Map<string, () => PaymentGateway>()
    export function registerGateway(key: string, factory: () => PaymentGateway) {
    registry.set(key, factory)
    }
    export function createGateway(key: string): PaymentGateway {
    const factory = registry.get(key)
    if (!factory) throw new Error(`Unknown gateway: ${key}`)
    return factory()
    }

    Visual explanation

    Three diagrams cover Factory Method flow, forms, and selection:

    Factory Method flow
    Client
    Calls creator.operation()
    factoryMethod()
    Subclass decides
    Product interface
    Returned type
    Use product
    Oblivious to concrete
    Client never calls new on a concrete class.
    Subclass vs registry
    Variant grows?
    Git evidence
    Class hierarchy
    Subclass Creator
    Plugin style
    Registry map
    Self-register
    import side-effect
    Registry is Factory Method without parallel class trees — same intent.
    Factory Method vs plain new
    One impl forever?
    new is honest
    2+ variants expected?
    Factory earns cost
    Non-trivial setup?
    Centralize wiring
    Tests need fake?
    Return interface
    Don't factory-wrap a type that will never vary.

    Informative example

    Before / after — switch sprawl to Factory Method:

    ts
    // ❌ Switch copied everywhere — OCP violation
    function charge(tenant: Tenant, amount: Money) {
    let gateway: PaymentGateway
    switch (tenant.provider) {
    case "stripe": gateway = new StripeGateway(tenant.keys); break
    case "adyen": gateway = new AdyenGateway(tenant.keys); break
    default: throw new Error("unknown")
    }
    return gateway.charge(amount)
    }
    // ✅ One creation point — new provider = new class + register line
    class PaymentGatewayFactory {
    constructor(private registry: Map<string, () => PaymentGateway>) {}
    create(provider: string): PaymentGateway {
    const factory = this.registry.get(provider)
    if (!factory) throw new UnknownProviderError(provider)
    return factory()
    }
    }
    // Bootstrap
    factory.register("stripe", () => new StripeGateway(config.stripe))
    factory.register("adyen", () => new AdyenGateway(config.adyen))

    Execution workflow

    1Factory Method lifecycle
    1 / 4

    Client invokes operation

    Calls Creator method — not a concrete constructor.

    Operation may use product internally.

    Real-world use

    Calendar.getInstance(), URLConnection.openConnection(), Spring BeanFactory, Angular ComponentFactoryResolver, React createElement, every test framework's SUT factory. Frameworks live on Factory Method because they construct types the author never saw.

    Production case study

    Multi-tenant storage backends:

    • Scenario: 6 storage backends; every file-write feature had a 6-way switch.
    • Decision: StorageGateway interface + per-tenant Factory Method at session start.
    • Outcome: 7th backend removed in one delete; 8th (Wasabi) shipped in 90 lines.
    • Metric: construction call sites dropped from 47 to 1.

    Trade-offs

    • Pro: new variants without editing stable code — OCP.
    • Pro: centralized construction and config wiring.
    • Con: indirection overkill if variant set is fixed forever.
    • Con: subclass form needs parallel Creator + Product hierarchies.

    Decision framework

    • Use when git log shows new variants arriving regularly.
    • Use when construction requires non-trivial setup you won't duplicate.
    • Avoid when one implementation forever — a plain constructor is honest.
    • Avoid when construction is one line — a function suffices.

    Best practices

    • Registry + self-register for plugin-style growth in TypeScript.
    • Return interfaces — never concrete types to consumers.
    • Spring @Bean methods are Factory Methods — treat them as architecture.

    Anti-patterns to avoid

    • God Factory with 40-way switch — split by domain or use registry.
    • Factory that returns concrete types — couples callers to implementations.
    • Factory Method "for consistency" on types that never vary.

    Common mistakes

    • Registry name collisions when two modules register the same key.
    • Forgetting to register in test bootstrap — obscure "unknown key" failures.
    • Subclass hierarchies when registry would be lighter.

    Debugging tips

    • Log factory key + concrete class at creation — trace wrong provider bugs.
    • Diff registry contents between prod and test containers.

    Optimization strategies

    • Cache factory products when construction is expensive and product is immutable.
    • Lazy registry init — register on first import of each plugin module.

    Common misconceptions

    • Factory Method ≠ Abstract Factory. Factory Method creates one product; Abstract Factory creates a family.
    • Factory Method ≠ Simple Factory. Simple factory is a function; Factory Method is the OCP pattern with override point.
    • DI containers are Factory Method registries at scale.

    Advanced interview questions

    Interview Prep

    Practice concise answers, then expand each card for the explanation.

    4 questions
    1BeginnerQuestionFactory Method vs new Foo()?+

    Answer

    new couples caller to concrete class. Factory returns interface — new variants are additions, not edits.

    Follow-up

    When keep new?
    2IntermediateQuestionFactory Method vs Abstract Factory?+

    Answer

    Factory Method: one product per call, one override method. Abstract Factory: family of related products from one factory object.

    Follow-up

    Example where Abstract Factory is overkill?
    3AdvancedQuestionRegistry vs subclass Factory Method in TypeScript?+

    Answer

    Same pattern intent. Registry avoids parallel class trees; each variant self-registers. Better for plugin growth.

    Follow-up

    What breaks on duplicate register?
    4AdvancedQuestionIntroduce Factory Method into 12-way switch legacy?+

    Answer

    Define product interface → extract each arm to class → build registry → replace switch with create(key) → delete switch with characterization tests.

    Follow-up

    First characterization test?

    Summary

    You can collapse switch sprawl into Factory Method, pick subclass vs registry form, and explain OCP payoff. Tell the carrier integration story — if you can do that, you own Factory Method.

    Ready to mark this lesson complete?Track your journey across the entire course.