iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…