Introduction to Factory Method Pattern
The Factory Method pattern defines an interface for creating objects but lets subclasses decide which classes to instantiate. It's one of the most commonly used creational patterns.
The Problem
Imagine you're building a shipping system that works with multiple carriers:
typescript1// Without Factory Method - tight coupling2function createShippingLabel(carrier: string, shipment: Shipment): ShippingLabel {3 if (carrier === 'usps') {4 const label = new USPSLabel();5 label.setBarcode(generateUSPSBarcode(shipment));6 label.setFormat('4x6');7 return label;8 } else if (carrier === 'fedex') {9 const label = new FedExLabel();10 label.setTrackingNumber(generateFedExTracking(shipment));11 label.setFormat('thermal');12 return label;13 } else if (carrier === 'ups') {14 const label = new UPSLabel();15 label.setMaxicode(generateUPSMaxicode(shipment));16 label.setFormat('4x6');17 return label;18 }19 throw new Error(`Unknown carrier: ${carrier}`);20}
Problems with this approach:
- Adding a new carrier requires modifying this function
- Label creation logic is scattered
- Hard to test - can't easily mock label creation
- Violates Open/Closed Principle
The Solution: Factory Method
Factory Method encapsulates object creation in subclasses:
typescript1// Product interface2interface ShippingLabel {3 print(): void;4 getTrackingNumber(): string;5}67// Creator abstract class8abstract class LabelFactory {9 // Factory method10 abstract createLabel(shipment: Shipment): ShippingLabel;1112 // Template method using the factory13 printLabel(shipment: Shipment): void {14 const label = this.createLabel(shipment);15 console.log(`Printing label: ${label.getTrackingNumber()}`);16 label.print();17 }18}1920// Concrete creators21class USPSLabelFactory extends LabelFactory {22 createLabel(shipment: Shipment): ShippingLabel {23 return new USPSLabel(shipment);24 }25}2627class FedExLabelFactory extends LabelFactory {28 createLabel(shipment: Shipment): ShippingLabel {29 return new FedExLabel(shipment);30 }31}
Pattern Structure
1┌─────────────────────────────────────┐2│ LabelFactory │3│ (Creator) │4│ ─────────────────────────────────── │5│ + createLabel(): ShippingLabel │ ◄─── Factory Method (abstract)6│ + printLabel(): void │ ◄─── Uses factory method7└──────────────┬──────────────────────┘8 │ extends9 ┌──────┴──────┐10 │ │11 ▼ ▼12┌───────────────┐ ┌───────────────┐13│USPSLabelFactory│ │FedExLabelFactory│14│───────────────│ │───────────────│15│+ createLabel()│ │+ createLabel()│16└───────┬───────┘ └───────┬───────┘17 │ creates │ creates18 ▼ ▼19┌───────────────┐ ┌───────────────┐20│ USPSLabel │ │ FedExLabel │21│ (Product) │ │ (Product) │22└───────────────┘ └───────────────┘
When to Use Factory Method
-
Unknown types at compile time
- When you don't know the exact type of objects you need to create
- The system needs to be extensible with new product types
-
Delegate creation to subclasses
- When a class wants its subclasses to specify the objects it creates
- When you want to localize the knowledge of which class gets created
-
Provide hooks for subclasses
- When you want to give subclasses a way to extend the creation process
Real-World Examples
Logging Frameworks
typescript1abstract class LoggerFactory {2 abstract createLogger(): Logger;34 log(message: string): void {5 const logger = this.createLogger();6 logger.log(message);7 }8}910class FileLoggerFactory extends LoggerFactory {11 createLogger(): Logger {12 return new FileLogger('/var/log/app.log');13 }14}1516class ConsoleLoggerFactory extends LoggerFactory {17 createLogger(): Logger {18 return new ConsoleLogger();19 }20}
Database Connections
typescript1abstract class DatabaseFactory {2 abstract createConnection(): DatabaseConnection;34 query(sql: string): Promise<Result> {5 const conn = this.createConnection();6 return conn.execute(sql);7 }8}
Factory Method vs Simple Factory
Simple Factory (not a GoF pattern):
typescript1class LabelFactory {2 static create(type: string): Label {3 switch (type) {4 case 'usps': return new USPSLabel();5 case 'fedex': return new FedExLabel();6 }7 }8}
Factory Method (GoF pattern):
typescript1abstract class LabelFactory {2 abstract createLabel(): Label; // Subclasses decide3}
| Aspect | Simple Factory | Factory Method |
|---|---|---|
| Inheritance | No | Yes, required |
| Extension | Modify factory | Add new subclass |
| Flexibility | Low | High |
| Complexity | Lower | Higher |
Benefits
- Single Responsibility - Creation logic in dedicated factory classes
- Open/Closed - Add new products without modifying existing code
- Loose Coupling - Client code works with interfaces
- Testability - Easy to mock factories in tests
Drawbacks
- Class Proliferation - Many small classes
- Complexity - Simple creation doesn't need this pattern
- Parallel Hierarchies - Products and factories grow together
Summary
Factory Method is ideal when:
- You need to create objects without specifying exact classes
- You want subclasses to control object creation
- You're building extensible frameworks or libraries
Next: Let's look at the structure and implementation details.