Skip to content

// Design Patterns · 01

Abstract Factory

Families of related objectsleads toOne factory per family

Create families of related objects without naming their classes.

How it fits together

Abstract Factory structure«client»CheckoutInvoice(order)EuRegionFactoryEuVatCalculatorEuInvoiceFormatter«interface»RegionFactoryCreateTax()CreateFormatter()UsRegionFactoryUsSalesTaxCalculatorUsInvoiceFormatter«interface»TaxCalculatorTax(order)«interface»InvoiceFormatterFormat(invoice)injecteduses products
uses / callsimplementscreates
  • Checkout uses / calls RegionFactory (injected)
  • EuRegionFactory implements RegionFactory
  • UsRegionFactory implements RegionFactory
  • RegionFactory creates TaxCalculator
  • RegionFactory creates InvoiceFormatter
  • Checkout uses / calls TaxCalculator (uses products)

Spot it

  • if (region == "EU") repeated across features
  • Objects from two families mixed by mistake
  • Adding a region means editing many classes

It costs

Adding a new kind of product means changing every factory.

In code

public interface ITaxCalculator { decimal Tax(Order order); }​public interface IInvoiceFormatter { string Format(Invoice invoice); }​public interface IRegionFactory{    ITaxCalculator CreateTax();    IInvoiceFormatter CreateFormatter();}​public sealed class EuRegionFactory : IRegionFactory{    public ITaxCalculator CreateTax() => new EuVatCalculator();    public IInvoiceFormatter CreateFormatter() => new EuInvoiceFormatter();}​public sealed class Checkout(IRegionFactory region){    public string Invoice(Order order)    {        var tax = region.CreateTax().Tax(order);        return region.CreateFormatter().Format(new Invoice(order, tax));    }}
Go deeperThe full explanation and what to ask for · 1 min readExpand

An abstract factory is an interface with one creation method per product in a family. Each concrete factory returns products that belong together, so the client cannot mix an EU tax rule with a US invoice layout, and it only ever sees the abstract product types.

It pays off only when there are real families that must stay consistent. One product per factory is just a factory method. Adding a new product type touches the interface and every implementation, so the list of products should be stable.

Factory Method decides one product, often by subclassing; Abstract Factory groups several factory methods behind one object. Concrete factories may build products with Builder or clone them from Prototypes, and one instance per app, picked at the composition root, is usually enough.

Ask for it

  • abstract factory
  • product family
  • concrete factory
  • abstract product
  • composition root

Checkout picks tax rules and invoice layout with if (region == "EU") in several places. Introduce a RegionFactory with CreateTax() and CreateFormatter(), one implementation per region (EU, US), choose it once from tenant config at startup, and test that each family is consistent.