Abstract Factory
Factory Method solves "which single object should I create." But some situations need more: a whole set of objects that all have to agree with each other. The Abstract Factory pattern answers that broader question: how do you create families of related objects, guaranteeing the pieces you get back are always from the same consistent family, without the client code ever naming a concrete class?
⚠️ The Problem
Imagine a UI toolkit that supports a Light theme and a Dark theme. Every theme needs a
matching Button and Checkbox — a light button should never end up
paired with a dark checkbox, because the colors, borders, and hover states would clash and look
broken. If widget-creation code just does new LightButton() here and
new DarkCheckbox() there based on scattered, independent conditionals, nothing
stops the two calls from disagreeing about which theme is active. The bug that results isn't a
crash — it's a screen that's half light-themed and half dark-themed, and it can slip in anywhere
a developer forgets to check the theme flag before creating a widget.
✅ The Solution
Define an abstract factory interface with one creation method per product type in the family
— createButton(), createCheckbox(). Each concrete factory
(LightThemeFactory, DarkThemeFactory) implements all of those
methods together, so grabbing one concrete factory guarantees every product it hands out
belongs to the same family. Client code holds a reference to the abstract factory type, asks it
for whatever pieces it needs, and never has the chance to accidentally mix a light button with
a dark checkbox — the factory itself enforces that the pieces always match.
Example 1 — a UI theme factory
// Abstract products
class Button {
render() { throw new Error("render() must be implemented"); }
}
class Checkbox {
render() { throw new Error("render() must be implemented"); }
}
// Concrete products — Light family
class LightButton extends Button {
render() { console.log("[Light button] white bg, dark text"); }
}
class LightCheckbox extends Checkbox {
render() { console.log("[Light checkbox] white bg, dark check"); }
}
// Concrete products — Dark family
class DarkButton extends Button {
render() { console.log("[Dark button] charcoal bg, light text"); }
}
class DarkCheckbox extends Checkbox {
render() { console.log("[Dark checkbox] charcoal bg, light check"); }
}
// Abstract factory — one method per product type
class UIThemeFactory {
createButton() { throw new Error("createButton() must be implemented"); }
createCheckbox() { throw new Error("createCheckbox() must be implemented"); }
}
class LightThemeFactory extends UIThemeFactory {
createButton() { return new LightButton(); }
createCheckbox() { return new LightCheckbox(); }
}
class DarkThemeFactory extends UIThemeFactory {
createButton() { return new DarkButton(); }
createCheckbox() { return new DarkCheckbox(); }
}
// Client — only ever talks to the abstract factory
function renderSettingsPanel(factory) {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
renderSettingsPanel(new DarkThemeFactory()); // every widget matches — guaranteed
renderSettingsPanel() never mentions LightButton or
DarkCheckbox by name. Whichever factory it's handed, every widget it produces during
that call belongs to the same theme — there's no code path where a light button and a dark
checkbox both get created for the same panel.
Example 2 — a secondary context: cross-platform native dialogs
// Abstract products
public interface IDialog {
void Show(string message);
}
public interface INativeButton {
void Click();
}
// Windows family
public class WindowsDialog : IDialog {
public void Show(string message) => Console.WriteLine($"[Win32 dialog] {message}");
}
public class WindowsButton : INativeButton {
public void Click() => Console.WriteLine("Win32 button click sound");
}
// Mac family
public class MacDialog : IDialog {
public void Show(string message) => Console.WriteLine($"[Cocoa dialog] {message}");
}
public class MacButton : INativeButton {
public void Click() => Console.WriteLine("Cocoa button click sound");
}
// Abstract factory
public interface IUIFactory {
IDialog CreateDialog();
INativeButton CreateButton();
}
public class WindowsUIFactory : IUIFactory {
public IDialog CreateDialog() => new WindowsDialog();
public INativeButton CreateButton() => new WindowsButton();
}
public class MacUIFactory : IUIFactory {
public IDialog CreateDialog() => new MacDialog();
public INativeButton CreateButton() => new MacButton();
}
public class SettingsWindow {
public static void Open(IUIFactory factory) {
var dialog = factory.CreateDialog();
var button = factory.CreateButton();
dialog.Show("Preferences saved");
button.Click();
}
}
// SettingsWindow.Open(new MacUIFactory()); // dialog + button both render natively as Mac
Same guarantee, different domain: SettingsWindow.Open() depends only on
IUIFactory. Pick WindowsUIFactory and every native control it produces
looks and sounds like Windows; pick MacUIFactory and everything matches macOS — the
two families can never get cross-wired.
👍 When To Use It
- Your system needs to work with multiple families of related products (themes, platforms, database drivers) and products from one family must never be mixed with another.
- You want to enforce, at the type level, that a set of related objects is always created together and stays consistent — not rely on developers remembering to check a flag every time.
- You expect to add whole new families later (a "High Contrast" theme, a Linux UI factory) without changing the code that consumes the abstract factory.
👎 When NOT To Use It
- When there's only one family of products and no realistic prospect of a second — the extra abstract factory/product interfaces are pure ceremony with no payoff.
- When the "family" only has one member (a single product type) — that's just Factory Method; Abstract Factory earns its keep specifically when multiple product types must travel together.
- When adding a new product type (not a new family) is common — every concrete factory has to grow a new method, which means touching every existing factory implementation, a real maintenance cost of this pattern.
❓ FAQ
How is this different from Factory Method?
Factory Method is a single method, usually overridden through inheritance, that produces
one product type. Abstract Factory is an interface with multiple creation
methods — one per product type — implemented together by composition, so that a whole
family of related objects (a button and a checkbox, a dialog and a button)
comes back consistent. In fact, each individual method inside an Abstract Factory
(createButton()) is often implemented as its own little Factory Method — the
two patterns compose rather than compete.
Do I always need a base "abstract factory" class or interface?
In statically typed languages it's typical (an interface or abstract class), but in dynamically typed languages like JavaScript or Python it's common to skip the formal interface and just pass around objects/functions that satisfy the expected shape — the discipline of "one factory produces a consistent family" is what matters, not a specific keyword.
What happens when I need to add a new product type, like a Slider, to every theme?
Every concrete factory (LightThemeFactory, DarkThemeFactory,
and any others) needs a new createSlider() method added. This is the pattern's
known weak spot: it's easy to add a new family (a new factory implementing the
existing methods) but expensive to add a new product type across all families at
once.
🙋 There Are No Dumb Questions
Q: Couldn't I just pass a "theme" string around and branch on it wherever I create a
widget?
A: You could, but then the "these must match" rule lives only in the discipline of whoever
writes each call site — exactly the bug described in the Problem section above. Abstract
Factory moves that rule into the type system: once you've picked a concrete factory, it is
structurally impossible to get a mismatched product out of it, because that factory only knows
how to build its own family.
✅ Quick Check
What does Abstract Factory guarantee that calling separate constructors for each widget does not?
Next up: what if a class's constructor has grown so many optional parameters that calling it correctly has become a guessing game? That's the problem the Builder pattern solves.