Factory Method and Abstract Factory
Concept
Both patterns exist to keep client code from depending on concrete classes it doesn't need to know about, but they solve that problem at different scopes. Factory Method defines a single creation method whose job is to decide which concrete class to instantiate, so callers ask for "a thing that satisfies this interface" and never see the concrete type. In the GoF's original inheritance-based formulation, a base class declares the factory method and subclasses override it to return different concrete products; in the far more common modern usage, it's simply a static/module-level function (PaymentProcessorFactory.create("stripe")) that switches on a discriminator — a provider name, a config value, a user choice — and returns the matching concrete implementation behind a shared interface.
Abstract Factory is Factory Method applied one level up: instead of one creation method producing one kind of product, an abstract factory interface bundles several related creation methods so a client can produce a whole family of objects designed to work together, and swapping which concrete factory is in play swaps the entire family at once. The canonical example is a cross-platform UI toolkit — a WinFactory and a MacFactory each implement createButton(), createCheckbox(), createScrollbar(); picking one factory at startup guarantees every widget created afterward matches visually, because nothing downstream can accidentally mix a Windows button with a Mac scrollbar.
The distinction that matters in practice: Factory Method varies one product type; Abstract Factory varies a consistent set of product types together. A codebase that reaches for Abstract Factory when it only ever has one product type varying is paying for a layer of indirection (a factory interface, a factory-of-factories) that Factory Method alone would have covered.
Tradeoffs
| Approach | Benefit | Cost |
|---|---|---|
| Factory Method (function/method returning an interface) | Client code depends only on the interface; adding a new concrete type doesn't touch call sites | An extra indirection layer for what might be a single if/switch in small codebases |
| Abstract Factory (interface bundling several factory methods) | Guarantees a whole family of related objects stays mutually consistent (can't mix products from different families) | More classes/interfaces to define and maintain; overkill when there's really only one product axis, not a family |
| Plain constructor call at each call site | Zero indirection, simplest to read for a fixed, small type | Every call site must know and import the concrete type; adding a new type means touching every call site individually |
if/switch on a type discriminator, inline |
No extra abstraction, fine for 2-3 stable options | Same switch logic tends to get duplicated at every place a new instance is needed, and grows unwieldy as options increase |
| DI container resolving an interface to a registered implementation | No factory code to write by hand; swapping implementations is a config/registration change | Requires a DI framework/container already in place; less explicit about how the right concrete type gets chosen |
When to use / when not to
- Use Factory Method when a class hierarchy has multiple concrete implementations selected at runtime by config, user input, or environment — e.g. picking a concrete
LLMProvider(OpenAI, Anthropic, local model) based on a config value, without every call site needing to know which provider is active. - Use Abstract Factory specifically when several related object types must be created together and stay mutually consistent — cross-platform UI kits, database-driver families (connection + statement + result-set objects that must all come from the same driver), theming systems.
- Don't reach for Abstract Factory when only one product type actually varies — that's Factory Method wearing an unnecessarily heavy hat.
- Don't introduce either pattern for a fixed, small, unlikely-to-grow set of types (2-3 concrete classes that will never gain a fourth) — a plain constructor call or a small
ifchain is more readable and has nothing to maintain.
Common pitfall
Reaching for Abstract Factory's extra structure (a factory interface plus a factory-of-factories) when the actual variation is one-dimensional — only the payment processor varies, say, with no second or third related object type that needs to travel with it. This over-engineers a Factory Method-shaped problem into an Abstract Factory-shaped solution, adding interfaces and indirection with no corresponding payoff. The reverse pitfall is just as common: leaving a genuinely open-ended, plugin-style creation point (new providers added regularly, by different teams, without touching shared code) as a growing inline if/switch chain scattered across the codebase, when a single Factory Method would have contained that growth to one place.
Engineering Lens
The real review question for either pattern isn't "did we use a factory class" — it's whether adding a new concrete type requires touching code that has nothing to do with that new type. A codebase where adding a new provider means editing five unrelated call sites' if chains has already paid Factory Method's complexity cost without getting its benefit (the Open/Closed Principle: open for extension, closed for modification). Conversely, an Abstract Factory introduced for a single product type is complexity paid for a consistency guarantee nothing actually needs yet — worth flagging in review just as much as the missing-factory case, since both directions cost future maintainers real time understanding indirection that isn't earning its keep.
Related
- Builder Pattern — another creational pattern, but Builder varies how one object is assembled step by step rather than which concrete class gets returned
- Strategy Pattern — both decouple client code from a concrete choice made elsewhere, but Strategy swaps a behavior/algorithm at runtime while a factory swaps which object gets constructed