Factory Pattern

intermediate 2-4 YOE lld design-pattern creational

The Factory pattern is about letting someone else create objects for us. Instead of calling new ConcreteClass() directly, we ask a factory to give us the right object.

Think of it like a pizza store. We walk in and say “I want a veggie pizza.” We don’t go into the kitchen and make it ourselves. The store (factory) knows exactly which class to instantiate, what ingredients to use, and how to assemble it.

The Problem It Solves

Without a factory, our code looks like this:

if (type === "email") notification = new EmailNotification();
else if (type === "sms") notification = new SMSNotification();
else if (type === "push") notification = new PushNotification();

This if-else mess gets scattered everywhere. Every time we add a new type, we hunt down every place that creates notifications. Painful.

A factory centralizes this creation logic in one place.

Factory Method vs Abstract Factory

Two Flavors of Factory
Factory Method
One method creates one type of object.

Subclasses decide which class to instantiate.

Example: createNotification() returns EmailNotification or SMSNotification
Abstract Factory
Creates families of related objects.

One factory produces a whole set of things that belong together.

Example: UIFactory creates Button + Checkbox + Input that all match (Material or iOS style)

The only difference is scope. Factory Method creates one product. Abstract Factory creates a family of products.

Factory Method — Notification Example

from abc import ABC, abstractmethod

# Product interface
class Notification(ABC):
    @abstractmethod
    def send(self, message: str) -> None:
        pass

# Concrete products
class EmailNotification(Notification):
    def send(self, message: str):
        print(f"Email: {message}")

class SMSNotification(Notification):
    def send(self, message: str):
        print(f"SMS: {message}")

class PushNotification(Notification):
    def send(self, message: str):
        print(f"Push: {message}")

# Factory
class NotificationFactory:
    @staticmethod
    def create(ntype: str) -> Notification:
        factories = {
            "email": EmailNotification,
            "sms": SMSNotification,
            "push": PushNotification,
        }
        if ntype not in factories:
            raise ValueError(f"Unknown type: {ntype}")
        return factories[ntype]()

# Usage -- we never call new EmailNotification() directly
notif = NotificationFactory.create("email")
notif.send("Hello!")  # Email: Hello!
// Product interface (via duck typing in JS)
class EmailNotification {
  send(message) { console.log(`Email: ${message}`); }
}

class SMSNotification {
  send(message) { console.log(`SMS: ${message}`); }
}

class PushNotification {
  send(message) { console.log(`Push: ${message}`); }
}

// Factory
class NotificationFactory {
  static create(type) {
    const factories = {
      email: EmailNotification,
      sms: SMSNotification,
      push: PushNotification,
    };
    const NotifClass = factories[type];
    if (!NotifClass) throw new Error(`Unknown type: ${type}`);
    return new NotifClass();
  }
}

// Usage
const notif = NotificationFactory.create("email");
notif.send("Hello!"); // Email: Hello!
// Product interface
interface Notification {
    void send(String message);
}

class EmailNotification implements Notification {
    public void send(String message) { System.out.println("Email: " + message); }
}

class SMSNotification implements Notification {
    public void send(String message) { System.out.println("SMS: " + message); }
}

class PushNotification implements Notification {
    public void send(String message) { System.out.println("Push: " + message); }
}

// Factory
class NotificationFactory {
    public static Notification create(String type) {
        return switch (type) {
            case "email" -> new EmailNotification();
            case "sms"   -> new SMSNotification();
            case "push"  -> new PushNotification();
            default -> throw new IllegalArgumentException("Unknown: " + type);
        };
    }
}

// Usage
// Notification notif = NotificationFactory.create("email");
// notif.send("Hello!");

Now if we add a WhatsAppNotification, we only change one place — the factory. Everything else stays untouched.

Abstract Factory — UI Theme Example

When we need a family of related objects that must be consistent with each other, we use Abstract Factory.

from abc import ABC, abstractmethod

# Abstract products
class Button(ABC):
    @abstractmethod
    def render(self): pass

class Checkbox(ABC):
    @abstractmethod
    def render(self): pass

# Material family
class MaterialButton(Button):
    def render(self): print("Material Button")

class MaterialCheckbox(Checkbox):
    def render(self): print("Material Checkbox")

# iOS family
class IOSButton(Button):
    def render(self): print("iOS Button")

class IOSCheckbox(Checkbox):
    def render(self): print("iOS Checkbox")

# Abstract Factory
class UIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button: pass
    @abstractmethod
    def create_checkbox(self) -> Checkbox: pass

class MaterialFactory(UIFactory):
    def create_button(self): return MaterialButton()
    def create_checkbox(self): return MaterialCheckbox()

class IOSFactory(UIFactory):
    def create_button(self): return IOSButton()
    def create_checkbox(self): return IOSCheckbox()

# Client code -- doesn't know which family it's using
def build_ui(factory: UIFactory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    button.render()
    checkbox.render()

build_ui(MaterialFactory())  # Material Button, Material Checkbox
// Material family
class MaterialButton {
  render() { console.log("Material Button"); }
}
class MaterialCheckbox {
  render() { console.log("Material Checkbox"); }
}

// iOS family
class IOSButton {
  render() { console.log("iOS Button"); }
}
class IOSCheckbox {
  render() { console.log("iOS Checkbox"); }
}

// Abstract Factories
class MaterialFactory {
  createButton() { return new MaterialButton(); }
  createCheckbox() { return new MaterialCheckbox(); }
}

class IOSFactory {
  createButton() { return new IOSButton(); }
  createCheckbox() { return new IOSCheckbox(); }
}

// Client code
function buildUI(factory) {
  const button = factory.createButton();
  const checkbox = factory.createCheckbox();
  button.render();
  checkbox.render();
}

buildUI(new MaterialFactory()); // Material Button, Material Checkbox
// Abstract products
interface Button { void render(); }
interface Checkbox { void render(); }

// Material family
class MaterialButton implements Button {
    public void render() { System.out.println("Material Button"); }
}
class MaterialCheckbox implements Checkbox {
    public void render() { System.out.println("Material Checkbox"); }
}

// iOS family
class IOSButton implements Button {
    public void render() { System.out.println("iOS Button"); }
}
class IOSCheckbox implements Checkbox {
    public void render() { System.out.println("iOS Checkbox"); }
}

// Abstract Factory
interface UIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

class MaterialFactory implements UIFactory {
    public Button createButton() { return new MaterialButton(); }
    public Checkbox createCheckbox() { return new MaterialCheckbox(); }
}

class IOSFactory implements UIFactory {
    public Button createButton() { return new IOSButton(); }
    public Checkbox createCheckbox() { return new IOSCheckbox(); }
}

When to Use

  • Factory Method: when we have one product type with multiple variants (notifications, shapes, parsers)
  • Abstract Factory: when we need families of related objects that must be used together (UI themes, database drivers)
  • When we want to decouple creation from usage
  • When the exact type is decided at runtime

When NOT to Use

  • For simple object creation — if there’s only one concrete class, a factory is overkill
  • When the creation logic never changes — don’t add abstraction for the sake of it

In simple language, a factory is a middleman. We tell it what we want, and it figures out how to make it. Factory Method is a single middleman for one product. Abstract Factory is a whole department that produces a matching set of products.