Design Patterns

Abstract Factory Design Pattern

I once built a UI toolkit that needed to render on both the web and a React Native app. I started with a single factory per component. By the third compon...

23 Mar 2024

Abstract Factory Design Pattern

I once built a UI toolkit that needed to render on both the web and a React Native app. I started with a single factory per component. By the third component type I was drowning in factory spaghetti.

The Abstract Factory fixes that. It gives you one interface that produces an entire family of related objects. You call createButton() and createInput() and the concrete factory decides whether those are HTML elements, React components, or anything else.

Think of it like a themed furniture store. You walk into the "Modern" showroom and everything matches — chairs, tables, lamps. Walk into "Rustic" and you get a different set, but they still go together. The abstract factory is the showroom contract.

Javascript
class Button {
  render() {
    throw new Error("render method must be implemented.");
  }
}

class HtmlButton extends Button {
  render() {
    console.log("HTML Button rendered.");
  }
}

class ReactButton extends Button {
  render() {
    console.log("React Button rendered.");
  }
}

class Input {
  render() {
    throw new Error("render method must be implemented.");
  }
}

class HtmlInput extends Input {
  render() {
    console.log("HTML Input rendered.");
  }
}

class ReactInput extends Input {
  render() {
    console.log("React Input rendered.");
  }
}

// Abstract Factory
class UIFactory {
  createButton() {
    throw new Error("createButton method must be implemented.");
  }
  createInput() {
    throw new Error("createInput method must be implemented.");
  }
}

class HtmlUIFactory extends UIFactory {
  createButton() { return new HtmlButton(); }
  createInput() { return new HtmlInput(); }
}

class ReactUIFactory extends UIFactory {
  createButton() { return new ReactButton(); }
  createInput() { return new ReactInput(); }
}

// Usage — client code never knows the concrete types
function buildForm(factory) {
  const button = factory.createButton();
  const input = factory.createInput();
  input.render();
  button.render();
}

buildForm(new HtmlUIFactory());
buildForm(new ReactUIFactory());

Button and Input are abstract products. HtmlButton, ReactButton, HtmlInput, ReactInput are the concrete variants. UIFactory is the abstract factory — it guarantees every implementation can produce a full, consistent set of UI elements.

The client code (buildForm) never mentions a concrete class. Swap the factory, swap the entire UI family.

The benefit: You get guaranteed consistency across a family of objects and zero coupling to concrete types.

The cost: Every new product type (say, Checkbox) means updating every factory. The class hierarchy grows fast. If your product families rarely change, this pattern adds ceremony you don't need.

Use Abstract Factory when you have multiple families of related objects and your client code must stay agnostic to the concrete implementations. If you only have one product type, the simpler Factory Method is enough.