codejump

AcademyDesign Patterns

Abstract Factory pattern

Guaranteeing a whole family of related objects ships together, the extra interface every product carries, and the mismatch when a factory returns the wrong family.

7 min readUpdated

3 exercises

Waiting at the end of this page.

Abstract Factory is what Factory Method grows into once one product isn't enough, when what's needed is a whole matched family of related objects, guaranteed never to mix.

A Factory Method returns one interchangeable product. An Abstract Factory returns several, a whole family that has to agree with each other, guaranteed by construction. Pick a factory once, and every product it hands you afterward belongs to the same family. There is no code path where a caller ends up mixing a piece from one family with a piece from another, because nothing ever calls the two families' constructors side by side.

The problem it solves

Your app supports a light and a dark theme. The theme is not one object, it is a set: buttons, checkboxes, inputs, modals, each with its own light and dark version.

If each component is built by its own factory function, nothing links them. One screen builds a dark button because a flag was read at the top, another builds a light checkbox because it read the flag later, after the user toggled it. The result is a screen with a dark button sitting next to a light checkbox.

  • The mismatch is not a crash. It is a visual bug that only appears in one combination and is easy to miss in review.
  • Every place that builds a piece has to remember to ask for the same variant.
  • Adding a third theme means finding all of those places again.

The fix is to choose the family once, in one object, and let that object build every piece. Callers never pick a variant, so they cannot pick inconsistently.

Structure

renderForm only knows the UIFactory interface. Each concrete factory builds a matched set of products, so a light button never meets a dark checkbox
renderForm only knows the UIFactory interface. Each concrete factory builds a matched set of products, so a light button never meets a dark checkbox

Four parts:

  • Abstract products (Button, Checkbox): the interfaces callers use.
  • Concrete products (LightButton, DarkCheckbox, and so on): one per product per family.
  • An abstract factory interface (UIFactory) with one creation method per product.
  • Concrete factories (LightUIFactory, DarkUIFactory): each returns products from one family only.
class LightButton { render() { return 'light-button'; } }
class LightCheckbox { render() { return 'light-checkbox'; } }
class DarkButton { render() { return 'dark-button'; } }
class DarkCheckbox { render() { return 'dark-checkbox'; } }

class LightUIFactory {
  createButton() { return new LightButton(); }
  createCheckbox() { return new LightCheckbox(); }
}
class DarkUIFactory {
  createButton() { return new DarkButton(); }
  createCheckbox() { return new DarkCheckbox(); }
}

function renderForm(factory) {
  return [factory.createButton().render(), factory.createCheckbox().render()];
}

// renderForm(new LightUIFactory()) → ['light-button', 'light-checkbox']
// renderForm(new DarkUIFactory())  → ['dark-button', 'dark-checkbox']

Nothing inside renderForm decides which family it is rendering. It only calls the two methods every UIFactory promises to have, and whichever factory it was handed decides what those methods return together. That is the guarantee a single Factory Method cannot give you: with one factory function per product, nothing stops a caller from building a light button next to a dark checkbox by mistake.

Where it is used in real systems

One choice at startup swaps the whole set of collaborators: real database, real mailer and real clock in production, in-memory fakes in tests
One choice at startup swaps the whole set of collaborators: real database, real mailer and real clock in production, in-memory fakes in tests
Use caseThe family being kept consistent
Theming and design systemsLight or dark, or a per-brand skin, across every component.
Cross-platform UIButtons, dialogs and menus that match Windows, macOS or the web.
Database dialectsConnection, query builder and migration runner that all speak Postgres, or all speak MySQL.
Cloud providersStorage, queue and email clients that all come from AWS, or all from Cloudflare.
Test environmentsRepositories, mailer and clock that are all real, or all in-memory fakes.
LocalizationDate, number and currency formatters that all follow the same locale.

The test environment row is the one worth remembering. Wiring a whole set of fakes together at startup, instead of mocking each dependency in each test, is Abstract Factory in daily use. The rule in every row is the same: the pieces only make sense together.

Abstract Factories you already use

  • Design system providers. A theme provider that hands every child component its colors and spacing is choosing a family once, at the top.
  • Database drivers. A Knex or TypeORM dialect gives you a connection, a query compiler and a schema builder that all match the same database.
  • Dependency injection modules. A NestJS or Angular module that registers "the production set" or "the test set" of providers is a factory for a family.
  • Cross-platform toolkits. Any library with a per-OS backend for windows, dialogs and file pickers.

The cost

Abstract Factory trades one kind of change for another.

  • Adding a new family is easy. Write one new factory and its products. No caller changes.
  • Adding a new product type is hard. Add a createSlider() and every existing factory has to implement it, or the code stops compiling.

So it fits when the families change often and the set of product types is stable. If it is the other way around, the pattern fights you.

When not to use it

  • Only one family exists. With a single theme, UIFactory is an interface with one implementation and no reason to exist.
  • The products do not need to match. If a light button next to a dark checkbox is harmless, plain functions or Factory Method are enough.
  • Two products, no growth. For one or two objects, a config value and a simple function are lighter.

What interviewers ask

  • "Abstract Factory versus Factory Method?" One product versus a family that must stay consistent.
  • "What problem does it solve?" Preventing mismatched products by making the family a single choice.
  • "What is its main drawback?" Adding a product type touches every factory.
  • "Where would you use it in a real backend?" Swapping a whole set of production dependencies for in-memory ones in tests.
  • "How does it relate to dependency injection?" A DI container often plays the role of the abstract factory, deciding which family of implementations to wire.

The mistake to avoid

An Abstract Factory whose product methods can return a type from the wrong family. Nothing catches that at runtime in a dynamically typed language, the two products just silently disagree the first time they are used together. Type the factory's methods with the abstract interfaces and keep each concrete factory returning only its own family.

Abstract Factory is one naming device among several an object-oriented language gives you for controlling how new gets called. The mechanism above is the same, syntax aside, whether the language is JavaScript, Python or Java.

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.