20 minlesson

Introduction to Factory Method Pattern

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:

typescript
1// Without Factory Method - tight coupling
2function 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:

  1. Adding a new carrier requires modifying this function
  2. Label creation logic is scattered
  3. Hard to test - can't easily mock label creation
  4. Violates Open/Closed Principle

The Solution: Factory Method

Factory Method encapsulates object creation in subclasses:

typescript
1// Product interface
2interface ShippingLabel {
3 print(): void;
4 getTrackingNumber(): string;
5}
6
7// Creator abstract class
8abstract class LabelFactory {
9 // Factory method
10 abstract createLabel(shipment: Shipment): ShippingLabel;
11
12 // Template method using the factory
13 printLabel(shipment: Shipment): void {
14 const label = this.createLabel(shipment);
15 console.log(`Printing label: ${label.getTrackingNumber()}`);
16 label.print();
17 }
18}
19
20// Concrete creators
21class USPSLabelFactory extends LabelFactory {
22 createLabel(shipment: Shipment): ShippingLabel {
23 return new USPSLabel(shipment);
24 }
25}
26
27class 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 method
7└──────────────┬──────────────────────┘
8 │ extends
9 ┌──────┴──────┐
10 │ │
11 ▼ ▼
12┌───────────────┐ ┌───────────────┐
13│USPSLabelFactory│ │FedExLabelFactory│
14│───────────────│ │───────────────│
15│+ createLabel()│ │+ createLabel()│
16└───────┬───────┘ └───────┬───────┘
17 │ creates │ creates
18 ▼ ▼
19┌───────────────┐ ┌───────────────┐
20│ USPSLabel │ │ FedExLabel │
21│ (Product) │ │ (Product) │
22└───────────────┘ └───────────────┘

When to Use Factory Method

  1. 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
  2. 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
  3. Provide hooks for subclasses

    • When you want to give subclasses a way to extend the creation process

Real-World Examples

Logging Frameworks

typescript
1abstract class LoggerFactory {
2 abstract createLogger(): Logger;
3
4 log(message: string): void {
5 const logger = this.createLogger();
6 logger.log(message);
7 }
8}
9
10class FileLoggerFactory extends LoggerFactory {
11 createLogger(): Logger {
12 return new FileLogger('/var/log/app.log');
13 }
14}
15
16class ConsoleLoggerFactory extends LoggerFactory {
17 createLogger(): Logger {
18 return new ConsoleLogger();
19 }
20}

Database Connections

typescript
1abstract class DatabaseFactory {
2 abstract createConnection(): DatabaseConnection;
3
4 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):

typescript
1class 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):

typescript
1abstract class LabelFactory {
2 abstract createLabel(): Label; // Subclasses decide
3}
AspectSimple FactoryFactory Method
InheritanceNoYes, required
ExtensionModify factoryAdd new subclass
FlexibilityLowHigh
ComplexityLowerHigher

Benefits

  1. Single Responsibility - Creation logic in dedicated factory classes
  2. Open/Closed - Add new products without modifying existing code
  3. Loose Coupling - Client code works with interfaces
  4. Testability - Easy to mock factories in tests

Drawbacks

  1. Class Proliferation - Many small classes
  2. Complexity - Simple creation doesn't need this pattern
  3. 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.