# Abstract Factory

> Create families of related objects through one interface so client code always gets a matching set and never names the concrete classes.

- kind: pattern
- category: creational
- aliases: Kit
- canonical: https://aipatterns.dev/design-patterns/abstract-factory
- inspired-by: https://refactoring.guru/design-patterns/abstract-factory

## Intent

Provide one interface with a creation method for each kind of object in a family, and implement it once per family. Client code asks the factory for its parts and gets a set that is guaranteed to fit together, without ever naming a concrete class.

## Forces

- **Objects that must match** vs **code that builds them separately**: mixing an EU tax calculator with a US invoice layout is a bug that compiles fine.
- **One switch per object** vs **one decision per family**: when every constructor call checks the same `market` flag, the flag leaks everywhere.
- **Adding a family** vs **touching every client**: a new market should be one new class, not edits across the codebase.
- **Adding a product kind** vs **a stable factory interface**: every new method on the factory must be implemented by every family.

## Therefore

**Group the creation of each family's objects behind one factory interface, pick the concrete factory once at startup, and pass it to the code that needs the products.**

The decision "which family?" is made exactly once, at the composition root. Everything downstream receives a factory (or the products it made) and works only with the product interfaces. Each product interface still matters on its own; the factory's job is only to guarantee consistency between them.

## Structure

```mermaid
classDiagram
  class InvoicingKit {
    <<interface>>
    +taxCalculator() TaxCalculator
    +formatter() InvoiceFormatter
  }
  class EuInvoicing {
    +taxCalculator() TaxCalculator
    +formatter() InvoiceFormatter
  }
  class UsInvoicing {
    +taxCalculator() TaxCalculator
    +formatter() InvoiceFormatter
  }
  class TaxCalculator {
    <<interface>>
    +taxCents(net, region) int
  }
  class VatCalculator {
    +taxCents(net, region) int
  }
  class SalesTaxCalculator {
    +taxCents(net, region) int
  }
  class InvoiceFormatter {
    <<interface>>
    +render(seq, net, tax) string
  }
  class Billing {
    -TaxCalculator tax
    -InvoiceFormatter fmt
    +issue(netCents, region) string
  }
  InvoicingKit <|.. EuInvoicing
  InvoicingKit <|.. UsInvoicing
  TaxCalculator <|.. VatCalculator
  TaxCalculator <|.. SalesTaxCalculator
  EuInvoicing ..> VatCalculator : creates
  UsInvoicing ..> SalesTaxCalculator : creates
  Billing ..> InvoicingKit : uses
  Billing --> InvoiceFormatter
```

### Participants

- **Abstract factory** (`InvoicingKit`): one creation method per product kind.
- **Concrete factories** (`EuInvoicing`, `UsInvoicing`): one per family; each returns products that belong together.
- **Abstract products** (`TaxCalculator`, `InvoiceFormatter`): the interfaces clients use.
- **Concrete products** (`VatCalculator`, `SalesTaxCalculator`, and the two formatters): one implementation per family per kind. In the example both calculators are one `RateTable` type configured with different rates; a product only needs its own class when its behavior differs.
- **Client** (`Billing`): receives a factory, asks for products, and never sees a concrete class.

## Use it when

- Several objects vary together along one axis (market, cloud provider, database vendor, test vs production), and mixing them is a bug.
- The same `if (market === "eu")` check appears next to several different constructor calls.
- You ship a library that must run against several backends, and each backend needs a matching set of adapters.
- Tests need to swap a whole set of collaborators (in-memory store, fake clock, fake queue) in one move.

## Do not use it when

- You have one family today and only imagine a second. That is [speculative generality](https://aipatterns.dev/refactoring/smells/speculative-generality.md). Use plain constructors and extract the factory when the second family arrives with real requirements.
- Only one product kind varies. Use [Factory Method](https://aipatterns.dev/design-patterns/factory-method.md), or pass that one object in.
- The products don't actually constrain each other. Independent choices deserve independent parameters, not a factory that bundles them.
- A dependency injection container or a composition-root function already wires the right implementations per environment. A hand-written factory on top duplicates that wiring.
- Product kinds change often. Every new creation method must be added to every concrete factory, which turns a small feature into a wide change.

## Application grill

**Inspect first:** the concrete classes that would form each family, and how callers choose among them today (constructor calls at the use site, a config value, an environment variable); whether a module or DI container already centralizes construction.

**Missing pieces:** none beyond the language: one factory module with one function per family. A DI container (Awilix, tsyringe, Spring, Google Wire) earns its place only when wiring is repeated across many entry points; do not introduce one for a single family.

**Ask before you build:**

- **Scale:** how many real families exist, and is a third actually planned (a second cloud, a second database vendor)? Two families with no third planned is a conditional, not a factory.
- **Performance:** is construction on a per-request path where the indirection must stay allocation-cheap?
- **Reliability:** must an unknown family fail at startup (validate the factory map) or at first use?
- **Budget:** who maintains the wiring when a family gains or loses a member?
- **Implementation:** how do tests obtain a family - through the factory like production, or by constructing test doubles directly?

## Implementation steps

1. List the product kinds and the families. Write them as a grid; every cell must be a real class you need, or the grid is speculative.
2. Extract an interface for each product kind from what clients actually call.
3. Declare the abstract factory with one creation method per product kind, returning the product interfaces.
4. Implement one concrete factory per family. Keep family-specific configuration (rates, prefixes) inside it.
5. Choose the concrete factory once, at the composition root, from configuration.
6. Change clients to take the factory (or the products) through their constructor, and delete the scattered family checks.

## Example

A billing service sells in the EU and the US. EU invoices need VAT by country and EU-style numbering; US invoices need state sales tax and a different layout. Using the EU calculator with the US layout must be impossible.

```ts title="invoicing.ts"
interface TaxCalculator {
  taxCents(netCents: number, region: string): number;
}
interface InvoiceFormatter {
  render(seq: number, netCents: number, taxCents: number): string;
}
interface InvoicingKit {
  taxCalculator(): TaxCalculator;
  formatter(): InvoiceFormatter;
}

const amount = (cents: number) => (cents / 100).toFixed(2);

function rateFor(rates: Record<string, number>, region: string): number {
  const rate = rates[region];
  if (rate === undefined) throw new Error(`no tax rate for ${region}`);
  return rate;
}

class EuInvoicing implements InvoicingKit {
  taxCalculator(): TaxCalculator {
    const vat = { DE: 0.19, FR: 0.2, IE: 0.23 };
    return { taxCents: (net, country) => Math.round(net * rateFor(vat, country)) };
  }
  formatter(): InvoiceFormatter {
    return {
      render: (seq, net, tax) =>
        `EU-${String(seq).padStart(6, "0")} net ${amount(net)} EUR, VAT ${amount(tax)} EUR, gross ${amount(net + tax)} EUR`,
    };
  }
}

class UsInvoicing implements InvoicingKit {
  taxCalculator(): TaxCalculator {
    const salesTax = { CA: 0.0725, NY: 0.04, TX: 0.0625 };
    return { taxCents: (net, state) => Math.round(net * rateFor(salesTax, state)) };
  }
  formatter(): InvoiceFormatter {
    return {
      render: (seq, net, tax) =>
        `Invoice #${seq}: subtotal $${amount(net)}, sales tax $${amount(tax)}, total $${amount(net + tax)}`,
    };
  }
}

class Billing {
  private readonly tax: TaxCalculator;
  private readonly fmt: InvoiceFormatter;
  private seq = 0;

  constructor(kit: InvoicingKit) {
    this.tax = kit.taxCalculator();
    this.fmt = kit.formatter();
  }

  issue(netCents: number, region: string): string {
    return this.fmt.render(++this.seq, netCents, this.tax.taxCents(netCents, region));
  }
}

// Composition root: the family is chosen once.
const kits: Record<string, InvoicingKit> = { eu: new EuInvoicing(), us: new UsInvoicing() };
console.log(new Billing(kits["eu"]).issue(12000, "DE"));
// EU-000001 net 120.00 EUR, VAT 22.80 EUR, gross 142.80 EUR
console.log(new Billing(kits["us"]).issue(12000, "CA"));
// Invoice #1: subtotal $120.00, sales tax $8.70, total $128.70
```

```python title="invoicing.py"
from dataclasses import dataclass
from typing import Protocol

class TaxCalculator(Protocol):
    def tax_cents(self, net_cents: int, region: str) -> int: ...

class InvoiceFormatter(Protocol):
    def render(self, seq: int, net_cents: int, tax_cents: int) -> str: ...

class InvoicingKit(Protocol):
    def tax_calculator(self) -> TaxCalculator: ...
    def formatter(self) -> InvoiceFormatter: ...

@dataclass(frozen=True)
class RateTable:
    rates: dict[str, float]

    def tax_cents(self, net_cents: int, region: str) -> int:
        if region not in self.rates:
            raise KeyError(f"no tax rate for {region}")
        return round(net_cents * self.rates[region])

class EuFormatter:
    def render(self, seq: int, net: int, tax: int) -> str:
        return f"EU-{seq:06d} net {net / 100:.2f} EUR, VAT {tax / 100:.2f} EUR, gross {(net + tax) / 100:.2f} EUR"

class UsFormatter:
    def render(self, seq: int, net: int, tax: int) -> str:
        return f"Invoice #{seq}: subtotal ${net / 100:.2f}, sales tax ${tax / 100:.2f}, total ${(net + tax) / 100:.2f}"

class EuInvoicing:
    def tax_calculator(self) -> TaxCalculator:
        return RateTable({"DE": 0.19, "FR": 0.20, "IE": 0.23})  # VAT by country

    def formatter(self) -> InvoiceFormatter:
        return EuFormatter()

class UsInvoicing:
    def tax_calculator(self) -> TaxCalculator:
        return RateTable({"CA": 0.0725, "NY": 0.04, "TX": 0.0625})  # sales tax by state

    def formatter(self) -> InvoiceFormatter:
        return UsFormatter()

class Billing:
    def __init__(self, kit: InvoicingKit) -> None:
        self._tax = kit.tax_calculator()
        self._fmt = kit.formatter()
        self._seq = 0

    def issue(self, net_cents: int, region: str) -> str:
        self._seq += 1
        return self._fmt.render(self._seq, net_cents, self._tax.tax_cents(net_cents, region))

KITS: dict[str, InvoicingKit] = {"eu": EuInvoicing(), "us": UsInvoicing()}

if __name__ == "__main__":
    print(Billing(KITS["eu"]).issue(12000, "DE"))
    # EU-000001 net 120.00 EUR, VAT 22.80 EUR, gross 142.80 EUR
    print(Billing(KITS["us"]).issue(12000, "CA"))
    # Invoice #1: subtotal $120.00, sales tax $8.70, total $128.70
```

```go title="invoicing.go"
package main

import (
	"fmt"
	"math"
)

type TaxCalculator interface {
	TaxCents(netCents int, region string) (int, error)
}

type InvoiceFormatter interface {
	Render(seq, netCents, taxCents int) string
}

type InvoicingKit interface {
	TaxCalculator() TaxCalculator
	Formatter() InvoiceFormatter
}

type RateTable map[string]float64

func (r RateTable) TaxCents(net int, region string) (int, error) {
	rate, ok := r[region]
	if !ok {
		return 0, fmt.Errorf("no tax rate for %s", region)
	}
	return int(math.Round(float64(net) * rate)), nil
}

type euFormatter struct{}

func (euFormatter) Render(seq, net, tax int) string {
	return fmt.Sprintf("EU-%06d net %.2f EUR, VAT %.2f EUR, gross %.2f EUR",
		seq, float64(net)/100, float64(tax)/100, float64(net+tax)/100)
}

type usFormatter struct{}

func (usFormatter) Render(seq, net, tax int) string {
	return fmt.Sprintf("Invoice #%d: subtotal $%.2f, sales tax $%.2f, total $%.2f",
		seq, float64(net)/100, float64(tax)/100, float64(net+tax)/100)
}

type EuInvoicing struct{}

func (EuInvoicing) TaxCalculator() TaxCalculator {
	return RateTable{"DE": 0.19, "FR": 0.20, "IE": 0.23}
}

func (EuInvoicing) Formatter() InvoiceFormatter { return euFormatter{} }

type UsInvoicing struct{}

func (UsInvoicing) TaxCalculator() TaxCalculator {
	return RateTable{"CA": 0.0725, "NY": 0.04, "TX": 0.0625}
}

func (UsInvoicing) Formatter() InvoiceFormatter { return usFormatter{} }

type Billing struct {
	tax TaxCalculator
	fmt InvoiceFormatter
	seq int
}

func NewBilling(kit InvoicingKit) *Billing {
	return &Billing{tax: kit.TaxCalculator(), fmt: kit.Formatter()}
}

func (b *Billing) Issue(netCents int, region string) (string, error) {
	tax, err := b.tax.TaxCents(netCents, region)
	if err != nil {
		return "", err
	}
	b.seq++
	return b.fmt.Render(b.seq, netCents, tax), nil
}

func main() {
	kits := map[string]InvoicingKit{"eu": EuInvoicing{}, "us": UsInvoicing{}}
	fmt.Println(NewBilling(kits["eu"]).Issue(12000, "DE"))
	// EU-000001 net 120.00 EUR, VAT 22.80 EUR, gross 142.80 EUR <nil>
	fmt.Println(NewBilling(kits["us"]).Issue(12000, "CA"))
	// Invoice #1: subtotal $120.00, sales tax $8.70, total $128.70 <nil>
}
```

```java title="Invoicing.java"
import java.util.Map;

public class Invoicing {
    interface TaxCalculator { long taxCents(long netCents, String region); }
    interface InvoiceFormatter { String render(int seq, long netCents, long taxCents); }

    interface InvoicingKit {
        TaxCalculator taxCalculator();
        InvoiceFormatter formatter();
    }

    record RateTable(Map<String, Double> rates) implements TaxCalculator {
        public long taxCents(long net, String region) {
            Double rate = rates.get(region);
            if (rate == null) throw new IllegalArgumentException("no tax rate for " + region);
            return Math.round(net * rate);
        }
    }

    static String amount(long cents) { return String.format("%.2f", cents / 100.0); }

    static final class EuInvoicing implements InvoicingKit {
        public TaxCalculator taxCalculator() {
            return new RateTable(Map.of("DE", 0.19, "FR", 0.20, "IE", 0.23));
        }
        public InvoiceFormatter formatter() {
            return (seq, net, tax) -> String.format("EU-%06d net %s EUR, VAT %s EUR, gross %s EUR",
                seq, amount(net), amount(tax), amount(net + tax));
        }
    }

    static final class UsInvoicing implements InvoicingKit {
        public TaxCalculator taxCalculator() {
            return new RateTable(Map.of("CA", 0.0725, "NY", 0.04, "TX", 0.0625));
        }
        public InvoiceFormatter formatter() {
            return (seq, net, tax) -> String.format("Invoice #%d: subtotal $%s, sales tax $%s, total $%s",
                seq, amount(net), amount(tax), amount(net + tax));
        }
    }

    static final class Billing {
        private final TaxCalculator tax;
        private final InvoiceFormatter fmt;
        private int seq = 0;

        Billing(InvoicingKit kit) {
            this.tax = kit.taxCalculator();
            this.fmt = kit.formatter();
        }

        String issue(long netCents, String region) {
            return fmt.render(++seq, netCents, tax.taxCents(netCents, region));
        }
    }

    public static void main(String[] args) {
        Map<String, InvoicingKit> kits = Map.of("eu", new EuInvoicing(), "us", new UsInvoicing());
        System.out.println(new Billing(kits.get("eu")).issue(12000, "DE"));
        // EU-000001 net 120.00 EUR, VAT 22.80 EUR, gross 142.80 EUR
        System.out.println(new Billing(kits.get("us")).issue(12000, "CA"));
        // Invoice #1: subtotal $120.00, sales tax $8.70, total $128.70
    }
}
```

## Trade-offs

**Gains**
- Products from different families can't be mixed by accident.
- The family decision lives in one place, and clients depend only on interfaces.
- A new family is one new factory plus its products, with no client changes.
- Tests can swap an entire environment by passing a different factory.

**Costs**
- A new product kind changes the factory interface and every implementation.
- Many small types: factories times products. With one family, all of it is overhead.
- An extra layer of indirection between the client and the object it uses.

## Relations

- [Factory Method](https://aipatterns.dev/design-patterns/factory-method.md) creates one product. An abstract factory is often a bundle of factory methods, one per product kind.
- [Builder](https://aipatterns.dev/design-patterns/builder.md) assembles one complex object step by step. Abstract Factory returns finished, related objects immediately.
- [Prototype](https://aipatterns.dev/design-patterns/prototype.md) can implement a concrete factory: store one configured instance per product and clone it, instead of writing a subclass per family.
- [Singleton](https://aipatterns.dev/design-patterns/singleton.md) is often (and usually needlessly) applied to concrete factories. A factory with no state can be a plain module-level value or injected once.
- [Bridge](https://aipatterns.dev/design-patterns/bridge.md) can use an abstract factory to create and configure the implementation side that matches an abstraction.
- [Facade](https://aipatterns.dev/design-patterns/facade.md) can replace an abstract factory when clients only need to hide how a subsystem's objects are created, not swap families.

## Agent checklist

- [ ] At least two families exist in code today, each with a real implementation of every product kind.
- [ ] Mixing products across families is a genuine bug, not just a style preference.
- [ ] The concrete factory is chosen exactly once, at the composition root, not inside business logic.
- [ ] Clients reference only product and factory interfaces. Search for leftover concrete class names and family flags.
- [ ] You checked whether the existing DI setup already covers this wiring before adding a factory layer.
