Abstract Factory
Provides an interface for creating families of related objects without specifying their concrete classes — guaranteeing that the products of the same family are always compatible with each other.
Intent
Define an interface (the abstract factory) that declares creation methods for each product of a family. Each concrete implementation of that interface — the concrete factory — creates a coherent variant of the products. The client code works exclusively with the abstract interfaces: of the factory and of the products. It never references concrete classes directly.
Cataloged by Gamma, Helm, Johnson and Vlissides in the book Design Patterns: Elements of Reusable Object-Oriented Software (1994), Abstract Factory belongs to the creational patterns category. It's often described as a "super-factory" or a "factory of factories", but the essence is simpler: coordinating the creation of multiple products that need to work together.
Problem
Imagine a GUI component toolkit that must work on two operating systems: Windows and macOS. Each OS has its own look and behavior for buttons, checkboxes, menus and windows. The central problem isn't creating an isolated component — it's guaranteeing that every component used together belongs to the same visual family. Mixing a Windows button with a macOS checkbox results in an inconsistent interface.
The naive solution — an if/switch scattered through the
client code choosing which concrete class to instantiate — creates two
serious problems:
- Coupling to the OS: the business code needs to know whether it's running on Windows or macOS, mixing platform decisions with presentation logic.
-
Guaranteed inconsistency: it only takes one developer
forgetting an
ifsomewhere to mix components from different families. The compiler doesn't catch this error.
Abstract Factory vs Factory Method
The pattern's most important distinction — and the one that causes the most confusion:
- Factory Method creates a single product. The variation is in which concrete class of that product gets instantiated, controlled by Creator subclasses. It's an extension point for one object type.
- Abstract Factory creates an entire family of related products. A single concrete factory is responsible for creating button, checkbox and menu — all from the same theme/platform. It's a coordination of multiple factory methods around a cohesive family.
In fact, Abstract Factory's concrete factories often implement their creation methods using the Factory Method pattern internally. Abstract Factory is, conceptually, a composition of Factory Methods grouped by family.
Solution
Abstract Factory organizes the code into five participants:
-
AbstractFactory (interface): declares the creation
methods for each product type of the family. E.g.:
createButton()andcreateCheckbox(). -
ConcreteFactory: implements AbstractFactory, creating
products of a specific variant. E.g.:
WindowsFactorycreatesWindowsButtonandWindowsCheckbox. -
AbstractProduct (interface per type): defines the
contract for each product type. E.g.:
Buttonwithrender(),Checkboxwithcheck(). -
ConcreteProduct: implements the AbstractProduct for
a variant. E.g.:
WindowsButton,MacOsButton. - Client: only uses the AbstractFactory and AbstractProduct interfaces. Receives the concrete factory via injection (constructor or parameter) — never instantiates it directly.
To switch the whole family (change from the Windows theme to macOS), you just provide a different concrete factory to the client. Not a single line of client code needs to change.
Structure
Simplified UML diagram with the per-platform UI kit example:
«interface» «interface»
Button Checkbox
┌─────────────┐ ┌─────────────┐
│ + render() │ │ + check() │
└─────────────┘ └─────────────┘
▲ ▲
┌──────┴───────┐ ┌───────┴────────┐
│ │ │ │
WindowsButton MacOsButton WindowsCheckbox MacOsCheckbox
(ConcreteProduct) (ConcreteProduct)
«interface»
UIFactory
┌──────────────────────────┐
│ + createButton(): Button │
│ + createCheckbox(): │
│ Checkbox │
└──────────────────────────┘
▲
┌─────────┴────────────────┐
│ │
WindowsFactory MacOsFactory
(ConcreteFactory) (ConcreteFactory)
createButton() createButton()
→ new WindowsButton() → new MacOsButton()
createCheckbox() createCheckbox()
→ new WindowsCheckbox() → new MacOsCheckbox()
Call flow:
client (receives UIFactory via injection)
│
│ const button = factory.createButton()
│ const checkbox = factory.createCheckbox()
│ button.render()
│ checkbox.check()
▼
Result: all components belong to the same family.
To switch platforms: inject a different factory.
Code examples
Example 1 — UI kit by theme (Light / Dark)
The abstract factory guarantees that button and checkbox are always from the same theme. The client renders the application without knowing which theme is active.
// ── AbstractProducts ─────────────────────────────────────────
interface Button {
render(): string;
}
interface Checkbox {
check(): string;
}
// ── ConcreteProducts — Light theme ───────────────────────────
class LightButton implements Button {
render(): string {
return "[LIGHT] White button rendered";
}
}
class LightCheckbox implements Checkbox {
check(): string {
return "[LIGHT] Light checkbox checked";
}
}
// ── ConcreteProducts — Dark theme ────────────────────────────
class DarkButton implements Button {
render(): string {
return "[DARK] Dark button rendered";
}
}
class DarkCheckbox implements Checkbox {
check(): string {
return "[DARK] Dark checkbox checked";
}
}
// ── AbstractFactory ───────────────────────────────────────────
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// ── ConcreteFactories ─────────────────────────────────────────
class LightFactory implements UIFactory {
createButton(): Button { return new LightButton(); }
createCheckbox(): Checkbox { return new LightCheckbox(); }
}
class DarkFactory implements UIFactory {
createButton(): Button { return new DarkButton(); }
createCheckbox(): Checkbox { return new DarkCheckbox(); }
}
// ── Client ───────────────────────────────────────────────────
// Receives the factory via injection — never instantiates products directly.
function renderApp(factory: UIFactory): void {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
console.log(button.render());
console.log(checkbox.check());
}
// Family selection happens at the application's edge, not in the client.
const theme: "light" | "dark" = "dark";
const factory: UIFactory = theme === "dark" ? new DarkFactory() : new LightFactory();
renderApp(factory);
// → [DARK] Dark button rendered
// → [DARK] Dark checkbox checked
<?php
// ── AbstractProducts ─────────────────────────────────────────
interface Button
{
public function render(): string;
}
interface Checkbox
{
public function check(): string;
}
// ── ConcreteProducts — Light theme ───────────────────────────
class LightButton implements Button
{
public function render(): string
{
return '[LIGHT] White button rendered';
}
}
class LightCheckbox implements Checkbox
{
public function check(): string
{
return '[LIGHT] Light checkbox checked';
}
}
// ── ConcreteProducts — Dark theme ────────────────────────────
class DarkButton implements Button
{
public function render(): string
{
return '[DARK] Dark button rendered';
}
}
class DarkCheckbox implements Checkbox
{
public function check(): string
{
return '[DARK] Dark checkbox checked';
}
}
// ── AbstractFactory ───────────────────────────────────────────
interface UIFactory
{
public function createButton(): Button;
public function createCheckbox(): Checkbox;
}
// ── ConcreteFactories ─────────────────────────────────────────
class LightFactory implements UIFactory
{
public function createButton(): Button { return new LightButton(); }
public function createCheckbox(): Checkbox { return new LightCheckbox(); }
}
class DarkFactory implements UIFactory
{
public function createButton(): Button { return new DarkButton(); }
public function createCheckbox(): Checkbox { return new DarkCheckbox(); }
}
// ── Client ───────────────────────────────────────────────────
function renderApp(UIFactory $factory): void
{
$button = $factory->createButton();
$checkbox = $factory->createCheckbox();
echo $button->render() . PHP_EOL;
echo $checkbox->check() . PHP_EOL;
}
// Family selection happens at the application's edge, not in the client.
$theme = 'dark';
$factory = $theme === 'dark' ? new DarkFactory() : new LightFactory();
renderApp($factory);
// → [DARK] Dark button rendered
// → [DARK] Dark checkbox checked
Example 2 — Database connection family (SQL / NoSQL)
The same principle applied to infrastructure: the factory guarantees that connection, query builder and transaction always belong to the same provider. Useful in tests, where the concrete factory points to an in-memory database.
// ── AbstractProducts ─────────────────────────────────────────
interface Connection {
connect(dsn: string): void;
close(): void;
}
interface QueryBuilder {
select(table: string): string;
}
// ── ConcreteProducts — PostgreSQL ─────────────────────────────
class PgConnection implements Connection {
connect(dsn: string): void { console.log(`[PG] Connected: ${dsn}`); }
close(): void { console.log("[PG] Connection closed"); }
}
class PgQueryBuilder implements QueryBuilder {
select(table: string): string {
return `SELECT * FROM "${table}";`; // → SELECT * FROM "users";
}
}
// ── ConcreteProducts — SQLite (tests) ─────────────────────────
class SqliteConnection implements Connection {
connect(dsn: string): void { console.log(`[SQLite] Connected: ${dsn}`); }
close(): void { console.log("[SQLite] Connection closed"); }
}
class SqliteQueryBuilder implements QueryBuilder {
select(table: string): string {
return `SELECT * FROM \`${table}\`;`; // → SELECT * FROM `users`;
}
}
// ── AbstractFactory ───────────────────────────────────────────
interface DatabaseFactory {
createConnection(): Connection;
createQueryBuilder(): QueryBuilder;
}
// ── ConcreteFactories ─────────────────────────────────────────
class PostgresFactory implements DatabaseFactory {
createConnection(): Connection { return new PgConnection(); }
createQueryBuilder(): QueryBuilder { return new PgQueryBuilder(); }
}
class SqliteFactory implements DatabaseFactory {
createConnection(): Connection { return new SqliteConnection(); }
createQueryBuilder(): QueryBuilder { return new SqliteQueryBuilder(); }
}
// ── Client ───────────────────────────────────────────────────
function runQuery(factory: DatabaseFactory, dsn: string): void {
const conn = factory.createConnection();
const qb = factory.createQueryBuilder();
conn.connect(dsn);
console.log(qb.select("users"));
conn.close();
}
// In production: PostgresFactory. In tests: SqliteFactory.
runQuery(new PostgresFactory(), "postgres://localhost/app");
// → [PG] Connected: postgres://localhost/app
// → SELECT * FROM "users";
// → [PG] Connection closed
<?php
// ── AbstractProducts ─────────────────────────────────────────
interface Connection
{
public function connect(string $dsn): void;
public function close(): void;
}
interface QueryBuilder
{
public function select(string $table): string;
}
// ── ConcreteProducts — PostgreSQL ─────────────────────────────
class PgConnection implements Connection
{
public function connect(string $dsn): void { echo "[PG] Connected: {$dsn}" . PHP_EOL; }
public function close(): void { echo "[PG] Connection closed" . PHP_EOL; }
}
class PgQueryBuilder implements QueryBuilder
{
public function select(string $table): string
{
return "SELECT * FROM \"{$table}\";"; // → SELECT * FROM "users";
}
}
// ── ConcreteProducts — SQLite (tests) ─────────────────────────
class SqliteConnection implements Connection
{
public function connect(string $dsn): void { echo "[SQLite] Connected: {$dsn}" . PHP_EOL; }
public function close(): void { echo "[SQLite] Connection closed" . PHP_EOL; }
}
class SqliteQueryBuilder implements QueryBuilder
{
public function select(string $table): string
{
return "SELECT * FROM `{$table}`;"; // → SELECT * FROM `users`;
}
}
// ── AbstractFactory ───────────────────────────────────────────
interface DatabaseFactory
{
public function createConnection(): Connection;
public function createQueryBuilder(): QueryBuilder;
}
// ── ConcreteFactories ─────────────────────────────────────────
class PostgresFactory implements DatabaseFactory
{
public function createConnection(): Connection { return new PgConnection(); }
public function createQueryBuilder(): QueryBuilder { return new PgQueryBuilder(); }
}
class SqliteFactory implements DatabaseFactory
{
public function createConnection(): Connection { return new SqliteConnection(); }
public function createQueryBuilder(): QueryBuilder { return new SqliteQueryBuilder(); }
}
// ── Client ───────────────────────────────────────────────────
function runQuery(DatabaseFactory $factory, string $dsn): void
{
$conn = $factory->createConnection();
$qb = $factory->createQueryBuilder();
$conn->connect($dsn);
echo $qb->select('users') . PHP_EOL;
$conn->close();
}
// In production: PostgresFactory. In tests: SqliteFactory.
runQuery(new PostgresFactory(), 'postgres://localhost/app');
// → [PG] Connected: postgres://localhost/app
// → SELECT * FROM "users";
// → [PG] Connection closed
When to use
- When the system must be independent of how its products are created: the client shouldn't know whether it's dealing with Windows, macOS, PostgreSQL or SQLite products.
- When the products must be used together: button and checkbox from the same theme, connection and query builder from the same database. The factory guarantees the consistency that a set of isolated Factory Methods wouldn't guarantee.
- When you want to expose only interfaces, not implementations: Abstract Factory is the classic mechanism for libraries that let the consumer swap the concrete implementation (e.g.: database driver).
- When new families will be added: to add a new theme or provider, you just create a new concrete factory — without touching the client code or the existing factories.
When to avoid
- When there's only one family and it will never vary: creating an AbstractFactory for a single concrete implementation is pure overengineering. Use a simple Factory Method or direct instantiation.
-
When adding new product types is frequent: Abstract
Factory suffers when extending products (adding a third component type,
e.g.
Menu): it requires changing the AbstractFactory and all concrete factories — violating Open/Closed for product-type extension. - In projects with DI containers: modern frameworks (NestJS, Spring, Laravel) manage families of implementations through modules and injection scopes. Manually reimplementing Abstract Factory is usually redundant.
Pros and cons
Pros
- Guarantees compatibility between products of the same family — makes it impossible to mix products from different families.
- Follows the Open/Closed Principle for new families: adding a new concrete factory doesn't change existing code.
- Follows the Single Responsibility Principle: each concrete factory takes care of creating an entire family.
- Fully decouples the client from concrete product classes.
- Makes it easier to swap the whole family at runtime (e.g. light/dark theme, test vs production environment).
Cons
- Adding a new product type (e.g. a third component in the family) requires modifying the AbstractFactory and all concrete factories — the pattern's most fragile point.
- Introduces a considerable hierarchy of classes and interfaces even for moderate cases.
- Can be hard to justify the cost when the families are few and stable.
Common pitfalls
1. Using Abstract Factory when there's only one family (overengineering)
Warning: If the system has only one set of concrete products and no real variation is planned, creating an AbstractFactory with a single ConcreteFactory adds ceremony without benefit. Start with a Factory Method or direct instantiation. Refactor to Abstract Factory when the second family actually shows up.
2. Confusing it with Factory Method
Factory Method manages the creation of a single product type
through Creator inheritance. Abstract Factory manages the creation of
a family of types through composition of creation methods.
If your "Abstract Factory" has only one create() method,
it's probably a Factory Method in disguise.
3. Product extension breaks every factory
When a new product type needs to be added to the family (e.g. adding
createMenu() to the UIFactory interface), every
existing concrete factory needs to be updated — even the ones that won't
use the new product. This is the pattern's structural cost. Assess whether
your system's main variation is in families (Abstract Factory is
a good fit) or in product types (Strategy or Factory Method may
be better).
4. Choosing the concrete factory in the wrong place
The choice of which concrete factory to use should happen as close as
possible to the application's edge (entry point, configuration file, DI
container). If the client code decides which factory to instantiate with
an if/switch based on configuration, the pattern loses half
its value — the client is still coupled to the factories' concrete classes.
Related patterns
Abstract Factory connects to several other creational patterns:
Factory Method is the building block of Abstract Factory: each creation method of the abstract factory is, conceptually, a Factory Method. Builder focuses on the step-by-step assembly process of a single complex object, while Abstract Factory creates entire families of simple objects at once. Prototype is an alternative when creating new objects is done by cloning existing instances — it can be used inside a factory to reduce the number of subclasses. Singleton is frequently applied to concrete factories: since the factory has no relevant state, generally only one instance is needed.