Skip to contentJMRG
All posts

Abstract Factory Pattern: Families of Related Objects

design-patternscreationalabstract-factory

Intent

Abstract Factory provides an interface for creating families of related objects without specifying their concrete classes. It guarantees that the products created are compatible with each other.

The Problem

Imagine a UI system that must support multiple themes. Each theme has buttons, inputs, and cards with consistent styles:

// Without pattern: dispersed theme logic
function createButton(theme: string) {
  if (theme === 'light') return new LightButton();
  if (theme === 'dark') return new DarkButton();
}

// What if someone mixes a LightButton with DarkInput?

The Solution: Abstract Factory

classDiagram
    class UIFactory {
        <<interface>>
        +createButton(): Button
        +createInput(): Input
    }

    class LightThemeFactory {
        +createButton(): LightButton
        +createInput(): LightInput
    }

    class DarkThemeFactory {
        +createButton(): DarkButton
        +createInput(): DarkInput
    }

    UIFactory <|.. LightThemeFactory
    UIFactory <|.. DarkThemeFactory

TypeScript Implementation

// Abstract products
interface Button {
  render(): string;
}

interface Input {
  render(): string;
}

// Abstract factory
interface UIFactory {
  createButton(label: string): Button;
  createInput(placeholder: string): Input;
}

// Concrete factory: Light Theme
class LightThemeFactory implements UIFactory {
  createButton(label: string): Button {
    return {
      render: () => `<button class="bg-white text-gray-900">${label}</button>`,
    };
  }

  createInput(placeholder: string): Input {
    return {
      render: () => `<input class="bg-white" placeholder="${placeholder}">`,
    };
  }
}

// Usage with dependency injection
class LoginForm {
  constructor(private factory: UIFactory) {}

  render(): string {
    const emailInput = this.factory.createInput('Email');
    const submitButton = this.factory.createButton('Login');

    return `<form>${emailInput.render()}${submitButton.render()}</form>`;
  }
}

// Client decides which factory to use
const factory = theme === 'dark' ? new DarkThemeFactory() : new LightThemeFactory();
const loginForm = new LoginForm(factory);

When to Use

Situation Recommendation
Multiple themes/skins ✅ Recommended
Cross-platform support ✅ Recommended
Families of related products ✅ Recommended
Single product without variants ❌ Use Factory Method

Conclusion

Abstract Factory guarantees consistency when creating families of related objects, avoiding incompatible combinations.