Chapter 03 · Article 10 of 55

Factory Method Pattern

Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Article outline14 sections on this page

Overview

Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Also Known As: Virtual Constructor

Category: Creational Design Pattern

Complexity: *** (Moderate)

The Factory Method pattern is one of the most widely used creational patterns in object-oriented design. Rather than calling a constructor directly, clients call a factory method - a method whose sole responsibility is to instantiate and return an object. The key insight is that the factory method is declared in a base class (or interface) and overridden in subclasses, allowing each subclass to produce a different type of product without the client knowing the concrete class being created.

This pattern embodies the Dependency Inversion Principle: high-level modules depend on abstractions (the product interface), not on concrete implementations. It also supports the Open/Closed Principle by allowing new product types to be introduced without modifying existing creator code.


Problem It Solves

Tight Coupling to Concrete Classes

Consider a logistics application that initially only handles road transport. The code is littered with new Truck() calls. When the business expands to sea transport, every location that creates a transport object must be found and modified. The client code becomes tightly coupled to concrete classes, making it fragile and resistant to change.

// Scattered throughout the codebase
if (type == "road") {
    transport = new Truck();
} else if (type == "sea") {
    transport = new Ship();
} else if (type == "air") {   // Added later  -  requires modifying existing code
    transport = new Airplane();
}

Violation of the Open/Closed Principle (OCP)

Every time a new transport type is added, existing code must be modified. This violates OCP - classes should be open for extension but closed for modification. The conditional logic grows, becomes error-prone, and spreads across multiple files.

Additional Problems Addressed

  • God classes that know about every concrete type in the system
  • Untestable code because concrete dependencies cannot be substituted with mocks
  • Parallel hierarchies that are difficult to maintain without a structured creation mechanism
  • Framework design where the framework cannot anticipate which objects the application-level code will need

When to Use / When NOT to Use

When to UseWhen NOT to Use
You don't know ahead of time the exact types of objects your code will work withObject creation is trivial and unlikely to change
You want to provide a way for users of your library/framework to extend its internal componentsThere is only one concrete product and no foreseeable variation
You want to save system resources by reusing existing objects instead of rebuilding them each timeThe overhead of additional classes outweighs the flexibility gained
You need to decouple product creation from product usageA simple constructor call with parameters suffices
You want to follow OCP - adding new types without modifying existing codeYou are working in a functional paradigm where closures/higher-order functions serve better
You have parallel class hierarchies that need coordinated creationPerformance is critical and virtual dispatch overhead is unacceptable
You need to centralize complex construction logic that varies by subtypeThe team is unfamiliar with OOP patterns and simpler approaches would be more maintainable

Key Concepts & Theory

Creator vs Product Hierarchy

The pattern involves two parallel hierarchies:

  1. Product Hierarchy - The interface/abstract class defining what gets created, plus its concrete implementations.
  2. Creator Hierarchy - The interface/abstract class declaring the factory method, plus concrete creators that override it to produce specific products.

The creator does NOT primarily exist to create products. It typically contains core business logic that operates on products returned by the factory method. The factory method decouples this logic from concrete product classes.

Parameterized Factory Methods

A variation where the factory method accepts a parameter to determine which product to create. This reduces the number of creator subclasses but introduces conditional logic within the factory method itself. It's a pragmatic middle ground between a pure Factory Method and a Simple Factory.

abstract createTransport(type: string): Transport

Factory Method vs Simple Factory vs Static Factory

  • Simple Factory: A single class with a creation method (often static). Not a true GoF pattern - it's a programming idiom. The creation logic lives in one place but cannot be overridden.
  • Factory Method: Uses inheritance. The creation method is abstract/virtual and overridden by subclasses. Each subclass decides what to instantiate.
  • Static Factory Method: A static method on the product class itself (e.g., Integer.valueOf(), LocalDate.of()). Not related to the GoF Factory Method - it's a naming convention for constructors with descriptive names.

The Hollywood Principle

Factory Method follows "Don't call us, we'll call you." The framework (creator) defines the skeleton of an algorithm and calls the factory method at the right time. Subclasses fill in the creation details without controlling the overall flow.


ASCII Class Diagram

┌─────────────────────────────┐          ┌─────────────────────────────┐
│       <<interface>>         │          │       <<interface>>         │
│         Creator             │          │         Product             │
├─────────────────────────────┤          ├─────────────────────────────┤
│                             │          │ + operation(): void         │
│ + factoryMethod(): Product  │─────────▶│                             │
│ + someOperation(): void     │          └─────────────────────────────┘
│                             │                     ▲            ▲
└─────────────────────────────┘                     │            │
          ▲            ▲                            │            │
          │            │                            │            │
          │            │                            │            │
┌─────────┴───────┐  ┌┴────────────────┐  ┌───────┴──────┐  ┌──┴──────────────┐
│ ConcreteCreatorA │  │ ConcreteCreatorB│  │ConcreteProductA│  │ConcreteProductB│
├─────────────────┤  ├─────────────────┤  ├──────────────┤  ├─────────────────┤
│ factoryMethod() │  │ factoryMethod() │  │ operation()  │  │ operation()     │
│ {               │  │ {               │  └──────────────┘  └─────────────────┘
│  return new     │  │  return new     │
│  ProductA()     │  │  ProductB()     │
│ }               │  │ }               │
└─────────────────┘  └─────────────────┘

Relationships:
  Creator ──────────▶ Product          (dependency: creates)
  ConcreteCreatorA ──▶ ConcreteProductA (instantiates)
  ConcreteCreatorB ──▶ ConcreteProductB (instantiates)

Pseudocode Implementation

BAD: Direct Instantiation with if-else

class LogisticsApp {
    method planDelivery(type: string) {
        var transport: Transport

        // Tight coupling  -  must modify this every time a new type is added
        if (type == "road") {
            transport = new Truck()
            transport.maxLoad = 10_000
            transport.fuelType = "diesel"
        } else if (type == "sea") {
            transport = new Ship()
            transport.maxLoad = 500_000
            transport.containerCount = 200
        } else if (type == "rail") {
            transport = new Train()          // OCP violation!
            transport.maxLoad = 80_000
            transport.carriageCount = 40
        } else {
            throw new Error("Unknown transport type")
        }

        transport.deliver(package)
    }
}

Problems: Single class knows about all concrete types. Adding Airplane requires modifying planDelivery(). Cannot test with mock transports. Violates SRP, OCP, and DIP.


GOOD: Factory Method Pattern

Pattern Structure

// --- Product Interface ---
interface Transport {
    method deliver(cargo: Cargo): void
    method getCapacity(): number
}

// --- Concrete Products ---
class Truck implements Transport {
    method deliver(cargo: Cargo) {
        print("Delivering by road in a truck")
    }
    method getCapacity() { return 10_000 }
}

class Ship implements Transport {
    method deliver(cargo: Cargo) {
        print("Delivering by sea in a ship")
    }
    method getCapacity() { return 500_000 }
}

// --- Creator (declares factory method) ---
abstract class Logistics {
    // Factory Method  -  subclasses override this
    abstract method createTransport(): Transport

    // Core business logic that uses the factory method
    method planDelivery(cargo: Cargo) {
        var transport = this.createTransport()

        // Works with ANY transport via the interface
        if (cargo.weight > transport.getCapacity()) {
            throw new Error("Cargo too heavy for this transport")
        }
        transport.deliver(cargo)
    }
}

// --- Concrete Creators ---
class RoadLogistics extends Logistics {
    method createTransport(): Transport {
        return new Truck()
    }
}

class SeaLogistics extends Logistics {
    method createTransport(): Transport {
        return new Ship()
    }
}

Client Code

// Client is decoupled from concrete classes
method main() {
    var logistics: Logistics

    if (config.route == "road") {
        logistics = new RoadLogistics()
    } else if (config.route == "sea") {
        logistics = new SeaLogistics()
    }

    // Client works with creator via abstract interface
    logistics.planDelivery(cargo)
}

Adding a New Type (No Existing Code Modified)

// New product
class Airplane implements Transport {
    method deliver(cargo: Cargo) {
        print("Delivering by air")
    }
    method getCapacity() { return 50_000 }
}

// New creator  -  existing classes untouched
class AirLogistics extends Logistics {
    method createTransport(): Transport {
        return new Airplane()
    }
}

Benefits: Adding AirLogistics requires zero changes to Logistics, RoadLogistics, SeaLogistics, or any client code that works with the Logistics abstraction. OCP is preserved.


Simple Factory vs Factory Method vs Abstract Factory

AspectSimple FactoryFactory MethodAbstract Factory
GoF Pattern?No (idiom/convention)YesYes
MechanismSingle class with conditional logicInheritance + polymorphismComposition of multiple factory methods
Who decides?The factory class (centralized)Subclasses (decentralized)The concrete factory object
ExtensibilityMust modify factory to add typesAdd new subclass (OCP compliant)Add new factory + product family
Number of productsOne product hierarchyOne product hierarchyMultiple related product families
ComplexityLowMediumHigh
Use caseFew types, unlikely to changeSingle product with many variantsFamilies of related objects (UI themes, DB drivers)
CouplingFactory coupled to all productsEach creator knows only its productFactory coupled to one family
TestingHard to mock creationEasy - substitute creator subclassEasy - substitute factory
ExampleShapeFactory.create("circle")Dialog.createButton()GUIFactory.createButton() + GUIFactory.createCheckbox()

Real-World Examples

1. Document Creation (Word/PDF)

abstract class DocumentCreator {
    abstract method createDocument(): Document
    
    method openDocument(path: string) {
        var doc = this.createDocument()
        doc.load(path)
        doc.render()
    }
}

class WordDocumentCreator extends DocumentCreator {
    method createDocument(): Document { return new WordDocument() }
}

class PDFDocumentCreator extends DocumentCreator {
    method createDocument(): Document { return new PDFDocument() }
}

Applications like LibreOffice use this pattern to support multiple document formats without coupling the editor framework to specific format implementations.

2. UI Frameworks (Cross-Platform Dialogs)

abstract class Dialog {
    abstract method createButton(): Button

    method render() {
        var okButton = this.createButton()
        okButton.setLabel("OK")
        okButton.onClick(this.closeDialog)
        okButton.render()
    }
}

class WindowsDialog extends Dialog {
    method createButton(): Button { return new WindowsButton() }
}

class MacDialog extends Dialog {
    method createButton(): Button { return new MacButton() }
}

class LinuxDialog extends Dialog {
    method createButton(): Button { return new LinuxButton() }
}

Qt, Swing, and .NET WinForms all use variations of this pattern to render platform-native widgets while keeping application logic platform-agnostic.

3. Logger Factories

abstract class LoggerFactory {
    abstract method createLogger(): Logger

    method log(message: string, level: Level) {
        var logger = this.createLogger()
        logger.write(formatMessage(message, level))
    }
}

class FileLoggerFactory extends LoggerFactory {
    method createLogger(): Logger { return new FileLogger("/var/log/app.log") }
}

class CloudLoggerFactory extends LoggerFactory {
    method createLogger(): Logger { return new CloudWatchLogger(config) }
}

Frameworks like SLF4J (Java), Python's logging module, and .NET's ILoggerFactory use factory-based creation to decouple application code from specific logging implementations.


Advantages & Disadvantages

AdvantagesDisadvantages
Eliminates tight coupling between creator and concrete productsIncreases total number of classes in the system
Single Responsibility Principle - creation code isolated in one placeCan be overkill for simple scenarios with few product types
Open/Closed Principle - new types added without modifying existing codeRequires clients to subclass the creator just to create a new product type
Enables unit testing via mock/stub productsAdds indirection that can make code harder to follow for newcomers
Provides hooks for subclasses - factory method is a controlled extension pointParallel hierarchy maintenance - every new product may need a new creator
Supports returning cached/pooled objects instead of always creating new onesParameterized variants reintroduce conditional logic (partial benefit)
Framework-friendly - frameworks define abstract creators, apps provide concrete onesIDE navigation ("go to implementation") becomes less straightforward

Constraints & Edge Cases

Class Explosion

Each new product type potentially requires a new creator subclass. In systems with dozens of product variants, this leads to a proliferation of small classes. Mitigation strategies:

  • Parameterized Factory Method - Accept a type parameter and use a registry/map instead of one-subclass-per-product.
  • Registration-Based Factories - Products register themselves (often via decorators or static initializers), and the factory method looks them up at runtime.

When Simple Factory Suffices

If you have a stable set of 2-3 product types that rarely change, a Simple Factory (or even direct instantiation) is perfectly acceptable. Don't apply Factory Method prophylactically - apply it when you observe the need for extensibility or when building a framework/library.

Registration-Based Factories

class TransportRegistry {
    var creators: Map<string, () => Transport> = {}

    method register(type: string, creator: () => Transport) {
        this.creators[type] = creator
    }

    method create(type: string): Transport {
        if (!this.creators.has(type)) throw new Error("Unknown type")
        return this.creators[type]()
    }
}

// Registration at startup
registry.register("road", () => new Truck())
registry.register("sea", () => new Ship())
registry.register("air", () => new Airplane())  // No code changes elsewhere

This approach combines the extensibility of Factory Method with the simplicity of a centralized factory. It's common in plugin architectures and dependency injection containers.

Edge Cases to Consider

  • Null/Default products - What happens when the factory method receives an unknown type? Return a NullObject, throw, or fall back to a default?
  • Thread safety - If the factory method caches/pools objects, ensure thread-safe access.
  • Circular dependencies - Creator depends on Product interface; if Product needs Creator context, design carefully to avoid cycles.
  • Serialization - Deserialized objects bypass factory methods. Ensure consistency if factories enforce invariants.

Interview Follow-ups

Q1: How does Factory Method differ from Abstract Factory?

A: Factory Method uses inheritance - a single method in a base class is overridden by subclasses to create one product. Abstract Factory uses composition - an object implements multiple factory methods to create a family of related products. Factory Method is about creating one object; Abstract Factory is about creating families of objects that must be used together.

Q2: Can Factory Method return an existing object instead of always creating a new one?

A: Yes. The factory method's contract is to return an appropriate object, not necessarily to create a new one. It can implement caching, object pooling, or flyweight patterns internally. Integer.valueOf() in Java is a well-known example that returns cached instances for values -128 to 127.

Q3: How would you implement Factory Method in a language without classes (e.g., Go, Rust)?

A: In Go, you'd use interfaces for the product and function types (or interfaces with a single method) for the creator. A factory function type type TransportFactory func() Transport serves as the "creator." Different functions implementing this signature act as concrete creators. In Rust, you'd use traits for both Product and Creator, with concrete structs implementing the Creator trait's factory method.

Hint-Only Questions

Q4: How would you combine Factory Method with Dependency Injection?

  • Hint 1: Think about who provides the concrete creator - the DI container can inject it.
  • Hint 2: Consider IFactory<T> as a registered service. The container resolves the appropriate factory, and the factory method creates the product. This avoids the Service Locator anti-pattern.

Q5: How would you handle versioning when factory methods need to create different versions of a product?

  • Hint 1: Consider a parameterized factory method that accepts a version identifier.
  • Hint 2: Think about the Strategy pattern - the factory method could delegate to a version-specific strategy, or you could maintain a registry keyed by version + type.

Counter Questions to Ask Interviewer

  1. "How many product types do we anticipate, and how frequently will new types be added?" - Determines whether Factory Method's overhead is justified or if Simple Factory suffices.

  2. "Are the products part of a single hierarchy, or do we need families of related objects?" - Distinguishes Factory Method from Abstract Factory.

  3. "Is this for a framework/library consumed by external teams, or internal application code?" - Framework code benefits more from Factory Method since you can't predict what concrete types users will need.

  4. "Do products require complex multi-step construction, or is instantiation straightforward?" - If construction is complex, Builder pattern might be more appropriate, possibly combined with Factory Method.

  5. "Are there cross-cutting concerns (logging, caching, validation) that should be applied during creation?" - Helps determine if the factory method should also act as a decorator/proxy point.


References & Whitepapers

  1. Gamma, E., Helm, R., Johnson, R., Vlissides, J. - Design Patterns: Elements of Reusable Object-Oriented Software (1994), Chapter 3: Creational Patterns, pp. 107-116. The definitive reference for Factory Method.

  2. Refactoring Guru - Factory Method - Excellent visual explanations, real-world analogies, and implementations in 10+ languages.

  3. Martin, R.C. - Agile Software Development: Principles, Patterns, and Practices (2002), Chapter 29: Factory Pattern. Discusses Factory Method in the context of SOLID principles.

  4. Bloch, J. - Effective Java (3rd Edition, 2018), Item 1: "Consider static factory methods instead of constructors." Clarifies the distinction between GoF Factory Method and Java's static factory idiom.

  5. Freeman, E., Robson, E. - Head First Design Patterns (2nd Edition, 2020), Chapter 4: The Factory Pattern. Accessible introduction with pizza factory examples.

  6. Fowler, M. - Inversion of Control Containers and the Dependency Injection Pattern - Context on how factories relate to DI.