Abstract Factory
Abstract Factory: a factory of factories. Produce families of related objects without binding code to concrete classes.
Design patterns — Abstract Factory
EXAMPLE
// ===== The shape =====
// Define a factory INTERFACE that creates a family of related objects.
// Concrete factories implement the interface for one variant.
// Client code uses the interface; the variant is chosen at boot.
// ===== A worked example: cross-platform UI =====
interface Button { render(): string; }
interface Checkbox { render(): string; }
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
class MacButton implements Button { render() { return '[Mac Button]'; } }
class MacCheckbox implements Checkbox { render() { return '[Mac Checkbox]'; } }
class MacUIFactory implements UIFactory {
createButton() { return new MacButton(); }
createCheckbox() { return new MacCheckbox(); }
}
class WindowsButton implements Button { render() { return '<Win Button>'; } }
class WindowsCheckbox implements Checkbox { render() { return '<Win Checkbox>'; } }
class WindowsUIFactory implements UIFactory {
createButton() { return new WindowsButton(); }
createCheckbox() { return new WindowsCheckbox(); }
}
// Client code (knows ONLY the interfaces):
function renderForm(ui: UIFactory) {
const button = ui.createButton();
const box = ui.createCheckbox();
return [button.render(), box.render()].join('\n');
}
// At boot, pick a factory:
const factory: UIFactory = process.platform === 'darwin' ? new MacUIFactory() : new WindowsUIFactory();
console.log(renderForm(factory));
// ===== Another worked example: data store family =====
interface UserRepo { findById(id: string): Promise<unknown>; }
interface OrderRepo { findByUser(id: string): Promise<unknown[]>; }
interface StorageFactory {
userRepo(): UserRepo;
orderRepo(): OrderRepo;
}
class PostgresStorage implements StorageFactory {
userRepo() { return new PostgresUserRepo(); }
orderRepo() { return new PostgresOrderRepo(); }
}
class FirestoreStorage implements StorageFactory {
userRepo() { return new FirestoreUserRepo(); }
orderRepo() { return new FirestoreOrderRepo(); }
}
const storage: StorageFactory = process.env.STORE === 'firestore' ? new FirestoreStorage() : new PostgresStorage();
const users = storage.userRepo();
const orders = storage.orderRepo();
// ===== Why Abstract Factory =====
// - Decouple clients from concrete product classes
// - Enforce family CONSISTENCY (you do not accidentally mix Mac button + Win checkbox)
// - Swap entire families behind one variable
// ===== Compared with =====
// Factory Method one product; ABstract Factory creates a FAMILY of products
// Builder constructs a complex object step by step; AF picks the type to build
// Strategy swaps ONE behaviour; AF swaps a coordinated set
// ===== When it earns its keep =====
// - Multi-tenant systems with per-tenant variants (Postgres for some, Firestore for others)
// - Cross-platform UI / driver / printer / hardware abstraction
// - Testing: provide a 'test factory' that returns in-memory fakes
// ===== When it is overkill =====
// - One variant; no real choice -> ordinary classes
// - Tiny app; the factory layer adds friction
// - 'I might need it later' -> wait for the second variant
// ===== Patterns to internalise =====
// - Interfaces FIRST; concretes plug in
// - Inject the factory at the edge; pass interfaces inside the system
// - Easy testing: a FakeStorage factory returns in-memory repos
// - Coordinate the FAMILY: never let clients new up products directly
// ===== Pitfalls =====
// - Premature abstraction without a second variant in sight
// - Factory interfaces too big -> add methods carefully
// - Mixing 'create' and 'manage' in one factory (Liskov / SRP slip)
// - Hidden global factory access (service locator antipattern)
Why it matters
Abstract Factory delivers families of related objects under one interface. Reach for it when a system has two or more variants of a coordinated set (UI for Mac/Win, store for Postgres/Firestore) and you want clients to depend on interfaces, not concretes. Without a real second variant, plain classes win.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// One factory that returns whole families of related objects.
class UiKit { button() {} input() {} }
class MaterialKit extends UiKit { button() { return new MdButton(); } }
class CupertinoKit extends UiKit { button() { return new IosButton(); } }
Try it Yourself »
Discussion
Loading…