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 Use | When NOT to Use |
|---|---|
| You don't know ahead of time the exact types of objects your code will work with | Object creation is trivial and unlikely to change |
| You want to provide a way for users of your library/framework to extend its internal components | There is only one concrete product and no foreseeable variation |
| You want to save system resources by reusing existing objects instead of rebuilding them each time | The overhead of additional classes outweighs the flexibility gained |
| You need to decouple product creation from product usage | A simple constructor call with parameters suffices |
| You want to follow OCP - adding new types without modifying existing code | You are working in a functional paradigm where closures/higher-order functions serve better |
| You have parallel class hierarchies that need coordinated creation | Performance is critical and virtual dispatch overhead is unacceptable |
| You need to centralize complex construction logic that varies by subtype | The 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:
- Product Hierarchy - The interface/abstract class defining what gets created, plus its concrete implementations.
- 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
| Aspect | Simple Factory | Factory Method | Abstract Factory |
|---|---|---|---|
| GoF Pattern? | No (idiom/convention) | Yes | Yes |
| Mechanism | Single class with conditional logic | Inheritance + polymorphism | Composition of multiple factory methods |
| Who decides? | The factory class (centralized) | Subclasses (decentralized) | The concrete factory object |
| Extensibility | Must modify factory to add types | Add new subclass (OCP compliant) | Add new factory + product family |
| Number of products | One product hierarchy | One product hierarchy | Multiple related product families |
| Complexity | Low | Medium | High |
| Use case | Few types, unlikely to change | Single product with many variants | Families of related objects (UI themes, DB drivers) |
| Coupling | Factory coupled to all products | Each creator knows only its product | Factory coupled to one family |
| Testing | Hard to mock creation | Easy - substitute creator subclass | Easy - substitute factory |
| Example | ShapeFactory.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
| Advantages | Disadvantages |
|---|---|
| Eliminates tight coupling between creator and concrete products | Increases total number of classes in the system |
| Single Responsibility Principle - creation code isolated in one place | Can be overkill for simple scenarios with few product types |
| Open/Closed Principle - new types added without modifying existing code | Requires clients to subclass the creator just to create a new product type |
| Enables unit testing via mock/stub products | Adds indirection that can make code harder to follow for newcomers |
| Provides hooks for subclasses - factory method is a controlled extension point | Parallel hierarchy maintenance - every new product may need a new creator |
| Supports returning cached/pooled objects instead of always creating new ones | Parameterized variants reintroduce conditional logic (partial benefit) |
| Framework-friendly - frameworks define abstract creators, apps provide concrete ones | IDE 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
-
"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.
-
"Are the products part of a single hierarchy, or do we need families of related objects?" - Distinguishes Factory Method from Abstract Factory.
-
"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.
-
"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.
-
"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
-
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.
-
Refactoring Guru - Factory Method - Excellent visual explanations, real-world analogies, and implementations in 10+ languages.
-
Martin, R.C. - Agile Software Development: Principles, Patterns, and Practices (2002), Chapter 29: Factory Pattern. Discusses Factory Method in the context of SOLID principles.
-
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.
-
Freeman, E., Robson, E. - Head First Design Patterns (2nd Edition, 2020), Chapter 4: The Factory Pattern. Accessible introduction with pizza factory examples.
-
Fowler, M. - Inversion of Control Containers and the Dependency Injection Pattern - Context on how factories relate to DI.
Related Topics
- Singleton Pattern - Often combined with Factory Method to ensure a single creator instance
- Abstract Factory Pattern - Extends Factory Method to families of related products
- Builder Pattern - For complex multi-step construction; can be returned by a factory method
- Prototype Pattern - Alternative creational approach using cloning instead of subclassing
- Template Method Pattern - Factory Method is a specialization of Template Method applied to object creation
- Strategy Pattern - Factories can use strategies to determine which product to create
- Dependency Injection - Modern alternative/complement to Factory Method for decoupling creation from usage