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

    Abstract Factory

    Abstract Factory produces a family of related objects designed to work together — not one product at a time.

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

    Introduction

    Abstract Factory produces a family of related objects designed to work together — not one product at a time. A MacFactory creates MacButton, MacScrollbar, MacWindow; a WindowsFactory creates the Windows family. In modern systems you'll see it wherever platform, tenant, or region determines a coordinated set: per-region payment + tax + currency, per-tenant UI theme + auth + storage.

    The story

    A fintech serving four regions needed payment, tax, currency, and notification implementations to match each country. Mixing US tax with EU payment rules caused a six-figure refund. A RegionFactory yielding the entire family per request made mismatched mixes a compile-time impossibility.

    The business problem

    Mixed-family construction causes invariant violations and costly incidents:

    • Incompatible combos: EU tax rules with US payment processor — silent until refund event.
    • Scattered coupling: each object type wired independently at call sites.
    • Family growth pain: new region means editing every construction point.
    • Review blindness: PR adds one member from wrong family — no type error.

    The problem teams faced

    Signals that Abstract Factory fits:

    • Several object families exist; members must come from the same family.
    • Consumer repeatedly couples to specific implementations across types.
    • Adding a new family means editing many call sites.
    • Runtime platform/tenant/region picks the whole coordinated set.

    Understanding the topic

    Intent: Provide an interface for creating families of related objects without specifying concrete classes.

    • AbstractFactory — createPayment(), createTax(), createNotifier() — one interface.
    • ConcreteFactory — EuFactory, UsFactory — each returns matching family members.
    • Client — receives AbstractFactory once; never mixes families.
    • Products — interfaces per role; concrete classes come in matched sets.

    Internal architecture

    Region factory interface:

    ts
    interface RegionFactory {
    payment(): PaymentGateway
    tax(): TaxCalculator
    currency(): CurrencyFormatter
    notifier(): Notifier
    }
    class EuFactory implements RegionFactory { /* all EU impls */ }
    class UsFactory implements RegionFactory { /* all US impls */ }
    class CheckoutHandler {
    constructor(private region: RegionFactory) {}
    async execute(cart: Cart) {
    const total = this.region.tax().apply(cart)
    await this.region.payment().charge(total)
    await this.region.notifier().sendReceipt(cart)
    }
    }

    Visual explanation

    Three diagrams contrast families, Factory Method, and selection:

    Abstract Factory family
    RegionFactory
    Abstract interface
    createPayment()
    Family member
    createTax()
    Same family
    createNotify()
    Coordinated set
    Pick factory once — all products guaranteed compatible.
    Factory Method vs Abstract Factory
    One product varies?
    Factory Method
    Family varies?
    Abstract Factory
    Single create()
    One method
    Multiple creates()
    Matched set
    Abstract Factory is a bundle of Factory Methods with a consistency guarantee.
    Request-scoped factory
    Request arrives
    Tenant / region known
    Resolve factory
    EuFactory | UsFactory
    Inject into handler
    Constructor
    Handler uses family
    Never mixes
    Factory often lives for request lifetime — not process lifetime.

    Informative example

    Before / after — mixed families to coordinated factory:

    ts
    // ❌ Mixed family — compiles, fails in production
    const payment = new StripeGateway() // US
    const tax = new EuVatCalculator() // EU — wrong combo
    const result = tax.apply(payment.charge(cart))
    // ✅ Factory guarantees family consistency
    interface CommerceFactory {
    payment(): PaymentGateway
    tax(): TaxCalculator
    }
    class UsCommerceFactory implements CommerceFactory {
    payment = () => new StripeGateway(usConfig)
    tax = () => new UsSalesTaxCalculator()
    }
    class EuCommerceFactory implements CommerceFactory {
    payment = () => new AdyenGateway(euConfig)
    tax = () => new EuVatCalculator()
    }
    function checkout(factory: CommerceFactory, cart: Cart) {
    const charged = factory.payment().charge(cart)
    return factory.tax().apply(charged)
    }

    Execution workflow

    1Abstract Factory lifecycle
    1 / 4

    Identify family

    List objects that must never mix (payment, tax, currency).

    Draw matrix: family × product role.

    Real-world use

    Cross-platform UI toolkits (Swing PLAF), JDBC driver families, cloud SDK environment bundles (AWS vs LocalStack factories for dev), theme systems (Material vs custom component sets), multi-tenant SaaS where tenant config selects storage + auth + billing as a set.

    Production case study

    Regional commerce factory:

    • Scenario: Four regions, four object roles — 16 independent wiring points.
    • Symptom: Six-figure refund from US tax + EU payment mismatch.
    • Decision: RegionFactory injected per request at API gateway.
    • Outcome: Wrong-family bugs eliminated; new region = one factory class.
    • Metric: wiring call sites 16 → 1 per handler.

    Trade-offs

    • Pro: family consistency enforced by construction API.
    • Pro: new family is additive — Open/Closed across roles.
    • Con: heavy when only one product type varies.
    • Con: interface explosion — N products × M families to maintain.

    Decision framework

    • Use when 2+ product roles must stay in the same family.
    • Use when families map to tenant, region, platform, or environment.
    • Avoid when only one object varies — Factory Method suffices.
    • Avoid when families are theoretical — wait for second real family in git.

    Best practices

    • Inject factory at request boundary — resolve once per request.
    • Keep product interfaces small — ISP applies inside families too.
    • Name factories after the dimension that varies (Region, Tenant, Theme).

    Anti-patterns to avoid

    • Abstract Factory for one product — use Factory Method instead.
    • God factory with 20 create methods — split by subdomain.
    • Client still importing concrete products alongside factory.

    Common mistakes

    • Adding a new product role requires updating every concrete factory — plan for it.
    • Over-abstracting before a second family exists in production.
    • Process-wide factory when family varies per request.

    Debugging tips

    • Log factory class name at request start — trace wrong-region bugs.
    • Integration test matrix: every factory × every handler path.

    Optimization strategies

    • Cache immutable factory products within request scope if create is expensive.
    • Lazy-init heavy family members — not every request needs all roles.

    Common misconceptions

    • Abstract Factory ≠ Factory Method collection without guarantee. The family invariant is the point.
    • Not every "Factory" class is Abstract Factory — need multiple coordinated product roles.

    Advanced interview questions

    Interview Prep

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

    4 questions
    1IntermediateQuestionAbstract Factory vs Factory Method?+

    Answer

    Factory Method: one product, one override. Abstract Factory: family of related products, multiple create methods, consistency guarantee.

    Follow-up

    Example where Abstract Factory is overkill?
    2AdvancedQuestionWhen does adding a product role hurt?+

    Answer

    Every concrete factory must implement the new create method — interface grows. Acceptable when families are stable; painful when product roles churn weekly.

    Follow-up

    Alternative when roles churn?
    3IntermediateQuestionRequest-scoped vs singleton factory?+

    Answer

    Singleton when family is process-wide (dev vs prod env). Request-scoped when family varies per tenant/region/user.

    Follow-up

    Scope for multi-tenant SaaS?
    4AdvancedQuestionHow prevent wrong-family bug at compile time?+

    Answer

    Handler depends on AbstractFactory interface only — never imports StripeGateway and EuVat in same module. Type system + DI boundary.

    Follow-up

    What if dynamic plugin loading?

    Summary

    You can distinguish Abstract Factory from Factory Method, design region/tenant factories, and explain the refund incident. Tell the four-region fintech story — if you can do that, you own Abstract Factory.

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