Understanding the Factory Method
Learn how the Factory Method pattern provides an elegant solution for object creation while maintaining flexibility and adhering to SOLID principles.

Introduction
The Factory Method is one of the most widely used creational design patterns in software development. It provides a way to delegate the instantiation logic to child classes, making your code more flexible and easier to maintain.
What is the Factory Method Pattern?
The Factory Method pattern defines an interface for creating objects, but lets subclasses decide which class to instantiate. It encapsulates object creation and provides a common interface for creating objects without specifying their exact classes.
The Problem
Imagine you're building an application that processes different types of documents. Initially, you only support PDF documents:
class DocumentProcessor {
processDocument() {
const doc = new PDFDocument();
doc.open();
doc.process();
doc.close();
}
}This works fine until you need to support Word documents, Excel spreadsheets, or other formats. Adding new document types requires modifying the existing code, violating the Open/Closed Principle.
The Solution: Factory Method
The Factory Method pattern solves this by introducing an abstract creator class with a factory method that subclasses override to create specific objects.
Structure
The pattern consists of four key components:
- Product Interface - Defines the interface of objects the factory method creates
- Concrete Products - Implement the Product interface
- Creator (Abstract Class) - Declares the factory method
- Concrete Creators - Override the factory method to return specific products
Implementation Example
Let's implement a document processing system using the Factory Method pattern:
// Product Interface
interface Document {
open(): void;
process(): void;
close(): void;
}
// Concrete Products
class PDFDocument implements Document {
open(): void {
console.log("Opening PDF document...");
}
process(): void {
console.log("Processing PDF content...");
}
close(): void {
console.log("Closing PDF document.");
}
}
class WordDocument implements Document {
open(): void {
console.log("Opening Word document...");
}
process(): void {
console.log("Processing Word content...");
}
close(): void {
console.log("Closing Word document.");
}
}
// Creator (Abstract Class)
abstract class DocumentProcessor {
// Factory Method
abstract createDocument(): Document;
// Template method using the factory method
processDocument(): void {
const doc = this.createDocument();
doc.open();
doc.process();
doc.close();
}
}
// Concrete Creators
class PDFProcessor extends DocumentProcessor {
createDocument(): Document {
return new PDFDocument();
}
}
class WordProcessor extends DocumentProcessor {
createDocument(): Document {
return new WordDocument();
}
}Usage
Now we can easily process different document types without modifying existing code:
// Client code
function processUserDocument(processor: DocumentProcessor) {
processor.processDocument();
}
// Usage
const pdfProcessor = new PDFProcessor();
processUserDocument(pdfProcessor);
// Output:
// Opening PDF document...
// Processing PDF content...
// Closing PDF document.
const wordProcessor = new WordProcessor();
processUserDocument(wordProcessor);
// Output:
// Opening Word document...
// Processing Word content...
// Closing Word document.Real-World Applications
The Factory Method pattern is used extensively in popular frameworks and libraries:
1. UI Frameworks
React's component creation can be seen as a factory method pattern:
abstract class ComponentFactory {
abstract createComponent(): ReactComponent;
render() {
const component = this.createComponent();
return component.render();
}
}2. Database Connections
Different database drivers use factory methods:
abstract class DatabaseConnection {
abstract createConnection(): Connection;
connect() {
const conn = this.createConnection();
conn.open();
return conn;
}
}
class PostgresConnection extends DatabaseConnection {
createConnection(): Connection {
return new PostgresDriver();
}
}
class MongoConnection extends DatabaseConnection {
createConnection(): Connection {
return new MongoDriver();
}
}3. Logging Systems
Creating different logger implementations:
abstract class LoggerFactory {
abstract createLogger(): Logger;
log(message: string) {
const logger = this.createLogger();
logger.write(message);
}
}
class FileLoggerFactory extends LoggerFactory {
createLogger(): Logger {
return new FileLogger();
}
}
class ConsoleLoggerFactory extends LoggerFactory {
createLogger(): Logger {
return new ConsoleLogger();
}
}Advantages
✅ Loose Coupling
The client code doesn't depend on concrete classes, only on interfaces.
✅ Single Responsibility Principle
Object creation logic is separated from business logic.
✅ Open/Closed Principle
New product types can be added without modifying existing code.
✅ Flexibility
Easy to extend with new product variants.
Disadvantages
⚠️ Increased Complexity
Introduces additional classes and interfaces.
⚠️ Overhead
Might be overkill for simple scenarios with few variants.
When to Use Factory Method
Consider using this pattern when:
- You don't know the exact types and dependencies of objects your code should work with
- You want to provide users with a way to extend internal components
- You want to save system resources by reusing existing objects instead of rebuilding them
- Your code deals with different types of objects that share a common interface
When NOT to Use Factory Method
Avoid this pattern when:
- You only have one product type and don't anticipate adding more
- The instantiation logic is simple and unlikely to change
- The added abstraction would make the code harder to understand
Comparison with Other Patterns
Factory Method vs Abstract Factory
- Factory Method creates one product through inheritance
- Abstract Factory creates families of related products through composition
Factory Method vs Builder
- Factory Method focuses on creating objects in one step
- Builder constructs complex objects step by step
Conclusion
The Factory Method pattern is a powerful tool for managing object creation in a flexible and maintainable way. By delegating instantiation to subclasses, it allows your code to remain open for extension while closed for modification.
While it does introduce additional complexity, the benefits of loose coupling and enhanced flexibility make it invaluable for systems that need to support multiple product variants or anticipate future extensions.
Remember: use this pattern when you need flexibility in object creation, but don't overengineer simple scenarios where a straightforward approach would suffice.