Abstract Factory
Abstract Factory groups the creation of several related objects behind one interface, so the objects a program builds always come from the same matching set.
Problem
A UI library needs to render buttons, checkboxes, and text fields that look native on Windows, macOS, and Linux. Each platform has its own widget classes, and a screen only looks right if every widget on it comes from the same platform's set - mixing a Windows-style button with a macOS-style checkbox breaks the illusion immediately.
If the screen-building code instantiates `WindowsButton`, `MacCheckbox`, and so on directly, two things go wrong. First, the code that assembles a screen is littered with `if (platform === 'mac')` branches wherever a widget is created. Second, nothing stops a careless call from grabbing a widget from the wrong platform's set - the compiler has no concept of "these classes belong together."
Solution
Give every product type in the family its own interface (`Button`, `Checkbox`) and write one implementation class per platform per product. Then introduce a factory interface with one creation method per product type - `createButton()`, `createCheckbox()` - and implement it once per platform: `WindowsFactory`, `MacFactory`, `LinuxFactory`. Each concrete factory only ever knows how to build its own platform's widgets, so it physically cannot hand out a mismatched pair.
The screen-assembly code receives a factory instance (chosen once, e.g., at startup, based on the detected OS) and calls its creation methods without ever naming a concrete widget class. Switching platforms means swapping which factory gets injected - nothing else in the code changes.
When to Use
- Reach for it when a system must stay agnostic to which concrete family of products it works with, and that family is chosen at runtime or configuration time.
- Reach for it when several related product types must always be used together as a matching set, and accidentally mixing variants would be a real bug.
- Reach for it when you already have a Factory Method for one product and realize a second, third, and fourth related product need the same variant-based creation logic.
Real-World Examples
- **Java AWT/Swing `UIManager`** - swapping the look-and-feel (Metal, Nimbus, or a platform-native one) swaps out an entire factory that produces matching buttons, scrollbars, and menus.
- **`document.createElement` behind DOM implementations** - different rendering engines (jsdom vs. a real browser) act as interchangeable factories producing element objects that all satisfy the same DOM interfaces.
- **Database driver packages in ORMs (e.g., TypeORM, Sequelize)** - selecting `postgres` vs. `mysql` in the config swaps in a whole family of dialect-specific connection, query, and transaction objects.
Structure
A factory interface exposes one creation method per product type in the family. Each concrete factory implements every method to produce one consistent variant of the whole family, while each product type has its own interface implemented separately per variant. Client code depends only on the factory and product interfaces, never on the concrete classes behind them.
Participants
Interface listing one creation method per product type the family needs, e.g. createButton() and createCheckbox().
In the diagram: AbstractFactory.
One class per platform, implementing every creation method so it only ever returns widgets belonging to that single platform.
In the diagram: MacFactory.
Interface shared by every platform's version of one widget type, letting client code treat them interchangeably.
In the diagram: AbstractProduct (Button, Checkbox).
A specific platform's implementation of a widget interface, instantiated only by the matching ConcreteFactory.
In the diagram: MacButton, MacCheckbox.
Receives one factory instance and builds every widget through it, never referencing a concrete widget or factory class by name.
In the diagram: Client.
class Button {
render() { throw new Error('render() must be implemented'); }
}
class Checkbox {
render() { throw new Error('render() must be implemented'); }
}
class WindowsButton extends Button {
render() { console.log('Rendering a Windows-style button.'); }
}
class WindowsCheckbox extends Checkbox {
render() { console.log('Rendering a Windows-style checkbox.'); }
}
class MacButton extends Button {
render() { console.log('Rendering a macOS-style button.'); }
}
class MacCheckbox extends Checkbox {
render() { console.log('Rendering a macOS-style checkbox.'); }
}
class UIFactory {
createButton() { throw new Error('createButton() must be implemented'); }
createCheckbox() { throw new Error('createCheckbox() must be implemented'); }
}
class WindowsFactory extends UIFactory {
createButton() { return new WindowsButton(); }
createCheckbox() { return new WindowsCheckbox(); }
}
class MacFactory extends UIFactory {
createButton() { return new MacButton(); }
createCheckbox() { return new MacCheckbox(); }
}
function renderScreen(factory) {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
renderScreen(new WindowsFactory());
renderScreen(new MacFactory());
Step 1 of 7
Two abstract product interfaces
`Button` and `Checkbox` each declare a `render()` that throws if not overridden - they define what every concrete widget must implement, without saying how.
Step 2 of 7
Windows-family concrete products
`WindowsButton` and `WindowsCheckbox` both implement `render()` with Windows-specific output - together they form one consistent visual family.
Step 3 of 7
Mac-family concrete products
`MacButton` and `MacCheckbox` mirror the Windows pair but render macOS-style output instead - a second, independent family of the same two product types.
Step 4 of 7
UIFactory declares one creation method per product
The abstract factory lists `createButton()` and `createCheckbox()` side by side, guaranteeing that any concrete factory produces a matching pair rather than one widget type alone.
Step 5 of 7
Concrete factories each build one whole family
`WindowsFactory` returns only Windows widgets and `MacFactory` returns only Mac widgets - each factory is committed to one family across both `createButton()` and `createCheckbox()`.
Step 6 of 7
renderScreen() works through the abstract factory only
This function calls `factory.createButton()` and `factory.createCheckbox()` without knowing which concrete factory it received - it never mentions `WindowsButton` or `MacButton` by name.
Step 7 of 7
Swapping the factory swaps the entire family
Passing `new WindowsFactory()` then `new MacFactory()` into the same `renderScreen()` call produces two fully different, internally consistent sets of widgets from identical calling code.
interface Button {
render(): void;
}
interface Checkbox {
render(): void;
}
class WindowsButton implements Button {
render(): void { console.log('Rendering a Windows-style button.'); }
}
class WindowsCheckbox implements Checkbox {
render(): void { console.log('Rendering a Windows-style checkbox.'); }
}
class MacButton implements Button {
render(): void { console.log('Rendering a macOS-style button.'); }
}
class MacCheckbox implements Checkbox {
render(): void { console.log('Rendering a macOS-style checkbox.'); }
}
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
class WindowsFactory implements UIFactory {
createButton(): Button { return new WindowsButton(); }
createCheckbox(): Checkbox { return new WindowsCheckbox(); }
}
class MacFactory implements UIFactory {
createButton(): Button { return new MacButton(); }
createCheckbox(): Checkbox { return new MacCheckbox(); }
}
function renderScreen(factory: UIFactory): void {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
renderScreen(new WindowsFactory());
renderScreen(new MacFactory());
interface Button { void render(); }
interface Checkbox { void render(); }
class WindowsButton implements Button { public void render() { System.out.println("Rendering a Windows-style button."); } }
class WindowsCheckbox implements Checkbox { public void render() { System.out.println("Rendering a Windows-style checkbox."); } }
class MacButton implements Button { public void render() { System.out.println("Rendering a macOS-style button."); } }
class MacCheckbox implements Checkbox { public void render() { System.out.println("Rendering a macOS-style checkbox."); } }
interface UIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WindowsFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
class MacFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
static void renderScreen(UIFactory factory) {
factory.createButton().render();
factory.createCheckbox().render();
}
renderScreen(new WindowsFactory());
renderScreen(new MacFactory());
interface IButton { void Render(); }
interface ICheckbox { void Render(); }
class WindowsButton : IButton { public void Render() => Console.WriteLine("Rendering a Windows-style button."); }
class WindowsCheckbox : ICheckbox { public void Render() => Console.WriteLine("Rendering a Windows-style checkbox."); }
class MacButton : IButton { public void Render() => Console.WriteLine("Rendering a macOS-style button."); }
class MacCheckbox : ICheckbox { public void Render() => Console.WriteLine("Rendering a macOS-style checkbox."); }
interface IUIFactory { IButton CreateButton(); ICheckbox CreateCheckbox(); }
class WindowsFactory : IUIFactory
{
public IButton CreateButton() => new WindowsButton();
public ICheckbox CreateCheckbox() => new WindowsCheckbox();
}
class MacFactory : IUIFactory
{
public IButton CreateButton() => new MacButton();
public ICheckbox CreateCheckbox() => new MacCheckbox();
}
static void RenderScreen(IUIFactory factory)
{
factory.CreateButton().Render();
factory.CreateCheckbox().Render();
}
RenderScreen(new WindowsFactory());
RenderScreen(new MacFactory());
from __future__ import annotations
from abc import ABC, abstractmethod
class Button(ABC):
@abstractmethod
def render(self) -> None: ...
class Checkbox(ABC):
@abstractmethod
def render(self) -> None: ...
class WindowsButton(Button):
def render(self) -> None:
print('Rendering a Windows-style button.')
class WindowsCheckbox(Checkbox):
def render(self) -> None:
print('Rendering a Windows-style checkbox.')
class MacButton(Button):
def render(self) -> None:
print('Rendering a macOS-style button.')
class MacCheckbox(Checkbox):
def render(self) -> None:
print('Rendering a macOS-style checkbox.')
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button: ...
@abstractmethod
def create_checkbox(self) -> Checkbox: ...
class WindowsFactory(UIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()
class MacFactory(UIFactory):
def create_button(self) -> Button:
return MacButton()
def create_checkbox(self) -> Checkbox:
return MacCheckbox()
def render_screen(factory: UIFactory) -> None:
button = factory.create_button()
checkbox = factory.create_checkbox()
button.render()
checkbox.render()
render_screen(WindowsFactory())
render_screen(MacFactory())
Step 1 of 7
Abstract products defined with ABC
`Button` and `Checkbox` are `ABC` subclasses with an `@abstractmethod render()` - Python refuses to instantiate any subclass that doesn't override it.
Step 2 of 7
Windows-family concrete products
`WindowsButton` and `WindowsCheckbox` both override `render()` with Windows-specific text, forming one matched family.
Step 3 of 7
Mac-family concrete products
`MacButton` and `MacCheckbox` implement the same two abstract classes with macOS-specific text instead - a second, independent family.
Step 4 of 7
UIFactory declares one creation method per product
`create_button()` and `create_checkbox()` are declared together as abstract methods, so any concrete factory must supply both halves of the family.
Step 5 of 7
Concrete factories each build one whole family
`WindowsFactory` returns only `Windows*` products and `MacFactory` returns only `Mac*` products - each factory commits to one consistent family.
Step 6 of 7
render_screen() works through the abstract factory only
The function receives a `UIFactory` and calls its two creation methods without ever naming a concrete class like `WindowsButton`.
Step 7 of 7
Swapping the factory swaps the entire family
Calling `render_screen(WindowsFactory())` then `render_screen(MacFactory())` produces two consistent, fully different widget sets from the same function.
Click "Run" to execute this code in a sandboxed frame and see console output here.
TypeScript runs as plain JavaScript here - type annotations are stripped, not type-checked.
Click "Run" to execute this code in a sandboxed frame and see console output here.
Advantages
- Every object a factory produces is guaranteed to belong to the same variant, so mismatched combinations become structurally impossible.
- Client code depends only on interfaces, so it never has to know or care which concrete classes are behind them.
- Adding an entirely new variant (a new platform, theme, or region) means adding one new factory class - existing code that consumes the abstraction stays untouched.
- Object-creation logic for a whole family lives in one place instead of being scattered across the codebase as conditionals.
Disadvantages
- Adding a brand-new product type (not just a new variant) forces you to touch the factory interface and every concrete factory that implements it.
- Small applications with only one product family gain little from the extra layer of interfaces and factory classes.
- Following the strict flow - factory interface, product interfaces, one implementation per cell of the matrix - front-loads a fair amount of boilerplate before any real logic is written.
Question 1 of 10
What role does the AbstractFactory interface play in the pattern's structure?
Correct! The AbstractFactory interface lists one creation method per product type the family needs, e.g. createButton() and createCheckbox(), guaranteeing every concrete factory produces a matching pair.
Not quite. The AbstractFactory interface lists one creation method per product type the family needs, e.g. createButton() and createCheckbox(), guaranteeing every concrete factory produces a matching pair.
Question 2 of 10
Which listed real-world example matches Abstract Factory's shape?
Correct! Swapping the look-and-feel in Java AWT/Swing swaps out an entire factory that produces matching buttons, scrollbars, and menus - the same family-of-related-objects shape as Abstract Factory.
Not quite. Swapping the look-and-feel in Java AWT/Swing swaps out an entire factory that produces matching buttons, scrollbars, and menus - the same family-of-related-objects shape as Abstract Factory.
Question 3 of 10
When is applying Abstract Factory said to bring little benefit?
Correct! Small applications with only one product family gain little from the extra layer of interfaces and factory classes, since there's no variant-mismatch risk to guard against.
Not quite. Small applications with only one product family gain little from the extra layer of interfaces and factory classes, since there's no variant-mismatch risk to guard against.
Question 4 of 10
In the UI library example, what goes wrong if screen-assembly code instantiates WindowsButton and MacCheckbox directly?
Correct! Direct instantiation litters the code with platform if/else branches, and the compiler has no concept that certain classes belong together, so a careless call can grab a mismatched widget.
Not quite. Direct instantiation litters the code with platform if/else branches, and the compiler has no concept that certain classes belong together, so a careless call can grab a mismatched widget.
Question 5 of 10
What does Abstract Factory guarantee about the objects a single concrete factory produces?
Correct! Each concrete factory (WindowsFactory, MacFactory) only knows how to build its own platform's widgets, so it physically cannot hand out a mismatched pair.
Not quite. Each concrete factory (WindowsFactory, MacFactory) only knows how to build its own platform's widgets, so it physically cannot hand out a mismatched pair.
Question 6 of 10
In the UI library problem, why does mixing a Windows-style button with a macOS-style checkbox matter?
Correct! A screen only looks right if every widget on it comes from the same platform's set - mixing widgets from different platforms breaks the illusion of a native UI immediately.
Not quite. A screen only looks right if every widget on it comes from the same platform's set - mixing widgets from different platforms breaks the illusion of a native UI immediately.
Question 7 of 10
According to the pattern's pros, what happens to object-creation logic for a whole product family?
Correct! Object-creation logic for a whole family lives in one place instead of being scattered across the codebase as conditionals, since each concrete factory owns the creation of its entire matching set.
Not quite. Object-creation logic for a whole family lives in one place instead of being scattered across the codebase as conditionals, since each concrete factory owns the creation of its entire matching set.
Question 8 of 10
In the cross-platform UI library, what does the Abstract Factory do with button, checkbox, and text-field creation?
Correct! It groups their creation behind one factory interface, so a screen built with a WindowsFactory only ever gets Windows-style widgets - never a stray macOS checkbox breaking the illusion.
Not quite. It groups their creation behind one factory interface, so a screen built with a WindowsFactory only ever gets Windows-style widgets - never a stray macOS checkbox breaking the illusion.
Question 9 of 10
What does the screen-assembly code depend on once Abstract Factory is applied?
Correct! Client code depends only on the factory and product interfaces, never on the concrete classes behind them; switching platforms just means injecting a different factory.
Not quite. Client code depends only on the factory and product interfaces, never on the concrete classes behind them; switching platforms just means injecting a different factory.
Question 10 of 10
According to the pattern's drawbacks, what happens when you need to add a brand-new product type rather than a new variant?
Correct! Adding a new variant (a new platform) is cheap - one new factory class - but a new product type forces edits to the factory interface and every existing concrete factory that implements it.
Not quite. Adding a new variant (a new platform) is cheap - one new factory class - but a new product type forces edits to the factory interface and every existing concrete factory that implements it.