StackPractices
beginner By Mathias Paulenko

Factory Pattern

Create objects without specifying the exact class to instantiate. A creational design pattern for flexible object creation.

Topics: design

Overview

The Factory Pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. Instead of calling a constructor directly, you call a factory method that returns a new instance based on input parameters.

This pattern is essential when the creation logic is complex, needs to be centralized, or must vary at runtime.

When to Use

Use the Factory Pattern when:

  • The exact type of object to create is determined at runtime
  • Object creation involves complex setup or configuration
  • You want to decouple object creation from usage
  • You need to support multiple implementations of an interface
  • Testing requires easy substitution of mock objects

Solution

Python

from abc import ABC, abstractmethod

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

class EmailNotification(Notification):
    def send(self, message: str) -> str:
        return f"Sending email: {message}"

class SmsNotification(Notification):
    def send(self, message: str) -> str:
        return f"Sending SMS: {message}"

class NotificationFactory:
    @staticmethod
    def create(channel: str) -> Notification:
        if channel == "email":
            return EmailNotification()
        if channel == "sms":
            return SmsNotification()
        raise ValueError(f"Unknown channel: {channel}")

# Usage
notifier = NotificationFactory.create("email")
print(notifier.send("Hello!"))  # Sending email: Hello!

JavaScript

class Notification {
  send(message) {
    throw new Error("Not implemented");
  }
}

class EmailNotification extends Notification {
  send(message) {
    return `Sending email: ${message}`;
  }
}

class SmsNotification extends Notification {
  send(message) {
    return `Sending SMS: ${message}`;
  }
}

class NotificationFactory {
  static create(channel) {
    switch (channel) {
      case "email": return new EmailNotification();
      case "sms": return new SmsNotification();
      default: throw new Error(`Unknown channel: ${channel}`);
    }
  }
}

const notifier = NotificationFactory.create("email");
console.log(notifier.send("Hello!")); // Sending email: Hello!

Java

public interface Notification {
    String send(String message);
}

public class EmailNotification implements Notification {
    public String send(String message) {
        return "Sending email: " + message;
    }
}

public class SmsNotification implements Notification {
    public String send(String message) {
        return "Sending SMS: " + message;
    }
}

public class NotificationFactory {
    public static Notification create(String channel) {
        switch (channel) {
            case "email": return new EmailNotification();
            case "sms": return new SmsNotification();
            default: throw new IllegalArgumentException("Unknown: " + channel);
        }
    }
}

// Usage
Notification notifier = NotificationFactory.create("email");
System.out.println(notifier.send("Hello!"));

Explanation

The Factory Pattern decouples object creation from usage through three roles:

  • Product Interface (Notification): Defines the contract all created objects must follow
  • Concrete Products (EmailNotification, SmsNotification): The actual implementations
  • Factory (NotificationFactory): Centralizes creation logic and returns the correct instance

This structure lets you add new notification channels (e.g., Push, Slack) without modifying the code that uses notifications.

Variants

VariantUse CaseTrade-off
Simple FactorySingle method with conditional logicEasy to start, hard to scale
Factory MethodSubclasses override creationMore flexible, more classes
Abstract FactoryFamilies of related objectsComplex, but handles product families

What Works

  • Use enums for channel/types instead of raw strings to avoid typos
  • Throw explicit errors for unsupported types instead of returning null
  • Keep the factory stateless when possible for thread safety
  • Register types dynamically in large systems (e.g., dependency injection containers)
  • Prefer interfaces over inheritance for the product contract

Common Mistakes

  • Over-engineering: Using Abstract Factory when Simple Factory is enough
  • String-based dispatch: Typo-prone and hard to refactor; use enums or constants
  • Stateful factories: Can cause thread-safety issues in multi-threaded environments
  • Null returns: Returning null instead of throwing makes bugs harder to trace
  • Tight coupling: Factory depending on concrete classes instead of abstractions

Troubleshooting

  • Pattern does not fit the problem: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
  • Too many abstractions: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
  • Tight coupling after refactoring: check that interfaces are stable and dependencies point inward.
  • Tests break when the design changes: favor stable contracts over internal structure.
  • Performance regression from indirection: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed.

Production Notes

  • Deploy gradually using canary or blue-green to catch regressions early.
  • Configure alerts for error rate, p99 latency, and failure rate before enabling in production.
  • Document the rollback in the runbook; test the procedure in staging at least once per quarter.
  • Review structured logs with correlation IDs to trace requests end-to-end during incidents.

Key Takeaways

  • Apply factory pattern when you need a practical solution for your use case.
  • Monitor performance after implementation; measure latency, errors, and resource usage before and after.
  • Check the Troubleshooting section for common failures; most have documented root causes with fixes.
  • Keep dependencies updated and run tests in CI to prevent production regressions.

Advanced Topics

Scenario: Factory for Multi-DB Connections

// Factory pattern: create objects without exposing creation logic
interface DatabaseConnection {
  connect(): Promise<void>;
  query(sql: string): Promise<unknown[]>;
  close(): Promise<void>;
}

class PostgreSQLConnection implements DatabaseConnection {
  constructor(private config: PGConfig) {}
  async connect() { /* pg.connect() */ }
  async query(sql: string) { /* pg.query(sql) */ return []; }
  async close() { /* pg.end() */ }
}

class MySQLConnection implements DatabaseConnection {
  constructor(private config: MySQLConfig) {}
  async connect() { /* mysql.connect() */ }
  async query(sql: string) { /* mysql.query(sql) */ return []; }
  async close() { /* mysql.end() */ }
}

class MongoDBConnection implements DatabaseConnection {
  constructor(private config: MongoConfig) {}
  async connect() { /* mongo.connect() */ }
  async query(sql: string) { /* mongo.find() */ return []; }
  async close() { /* mongo.close() */ }
}

// Factory
class DatabaseFactory {
  static create(type: "postgres" | "mysql" | "mongo", config: unknown): DatabaseConnection {
    switch (type) {
      case "postgres": return new PostgreSQLConnection(config as PGConfig);
      case "mysql": return new MySQLConnection(config as MySQLConfig);
      case "mongo": return new MongoDBConnection(config as MongoConfig);
      default: throw new Error(`Unknown DB type: ${type}`);
    }
  }
}

// Usage: client does not know which DB is used
const db = DatabaseFactory.create("postgres", {
  host: "localhost",
  port: 5432,
  database: "myapp",
});
await db.connect();
const results = await db.query("SELECT * FROM users");
await db.close();

// Factory with registry (plugin pattern)
class PluginDatabaseFactory {
  private static registry = new Map<string, (config: unknown) => DatabaseConnection>();

  static register(type: string, creator: (config: unknown) => DatabaseConnection) {
    this.registry.set(type, creator);
  }

  static create(type: string, config: unknown): DatabaseConnection {
    const creator = this.registry.get(type);
    if (!creator) throw new Error(`Unknown DB type: ${type}`);
    return creator(config);
  }
}

// Register plugins
PluginDatabaseFactory.register("postgres", (cfg) => new PostgreSQLConnection(cfg as PGConfig));
PluginDatabaseFactory.register("mysql", (cfg) => new MySQLConnection(cfg as MySQLConfig));

Lessons:

  • Factory encapsulates creation: client does not know concrete class
  • Adding new DB type only requires modifying the factory
  • Plugin factory allows registering types without modifying factory
  • Factory vs Abstract Factory: one class vs family of products
  • Factory vs DI: factory is explicit, DI is automatic

### Factory vs Abstract Factory: which do I use?

Use Factory when you need to create one type of object with variants (e.g: DatabaseConnection with postgres/mysql/mongo). Use Abstract Factory when you need to create a family of related objects (e.g: UI toolkit that creates Button + Input + Modal, all from the same theme). Factory has one create method; Abstract Factory has multiple create methods for related products.


End of document. Review and update quarterly.

## Common Production Pitfalls

- Applying the pattern where no abstraction is needed, adding accidental complexity.
- Letting the pattern leak into unrelated modules and blur ownership boundaries.
- Over-engineering the first implementation instead of starting simple and measuring pain.
- Skipping contract tests, so refactors silently break consumers.
- Ignoring failure modes that the pattern does not cover.
- Using the pattern as a default instead of choosing the right tool for the current scale.
- Forgetting to document when to stop using the pattern and what replaces it.
- Missing observability around the pattern's performance and error propagation.

Frequently Asked Questions

What is the difference between Factory Method and Abstract Factory?

Factory Method lets subclasses decide which class to instantiate. Abstract Factory creates families of related objects (e.g., UI components for Windows vs. Mac).

Is the Factory Pattern the same as dependency injection?

No. DI is about who provides the dependency; Factory is about how the dependency is created. They often work together.

When should I avoid the Factory Pattern?

Avoid it when object creation is trivial (a simple new Class()) and there is only one implementation.