Factory Method in TypeScript
TypeScript offers several ways to implement Factory Method, from classic inheritance to more functional approaches.
Classic Implementation
The traditional OOP approach:
typescript1// Product interface2interface ShippingLabel {3 trackingNumber: string;4 carrier: string;5 print(): void;6}78// Creator with factory method9abstract class LabelFactory {10 abstract createLabel(shipment: Shipment): ShippingLabel;1112 processShipment(shipment: Shipment): void {13 const label = this.createLabel(shipment);14 label.print();15 }16}1718// Concrete creator19class FedExLabelFactory extends LabelFactory {20 createLabel(shipment: Shipment): ShippingLabel {21 return new FedExLabel(shipment);22 }23}
Generic Factory Method
Use generics for type-safe factories:
typescript1interface Product {2 id: string;3}45abstract class Factory<T extends Product> {6 abstract create(data: unknown): T;78 createMany(dataList: unknown[]): T[] {9 return dataList.map(data => this.create(data));10 }11}1213// Type-safe concrete factory14interface Package extends Product {15 weight: number;16}1718class PackageFactory extends Factory<Package> {19 create(data: { id: string; weight: number }): Package {20 return {21 id: data.id,22 weight: data.weight23 };24 }25}2627const factory = new PackageFactory();28const pkg = factory.create({ id: 'PKG001', weight: 10 });29// pkg is typed as Package
Interface-Based Factory
Use interfaces instead of abstract classes:
typescript1interface LabelFactory {2 createLabel(shipment: Shipment): ShippingLabel;3}45class USPSLabelFactory implements LabelFactory {6 createLabel(shipment: Shipment): ShippingLabel {7 return new USPSLabel(shipment);8 }9}1011// Factory function that works with any implementation12function processWithFactory(13 factory: LabelFactory,14 shipment: Shipment15): void {16 const label = factory.createLabel(shipment);17 label.print();18}
Functional Factory
TypeScript supports functional programming approaches:
typescript1// Factory as a function type2type LabelCreator = (shipment: Shipment) => ShippingLabel;34// Factory functions5const createUSPSLabel: LabelCreator = (shipment) => new USPSLabel(shipment);6const createFedExLabel: LabelCreator = (shipment) => new FedExLabel(shipment);78// Higher-order function for processing9function withLogging(creator: LabelCreator): LabelCreator {10 return (shipment) => {11 console.log('Creating label...');12 const label = creator(shipment);13 console.log(`Created: ${label.trackingNumber}`);14 return label;15 };16}1718const loggedFedEx = withLogging(createFedExLabel);
Factory with Constructor Types
Pass class constructors as factories:
typescript1interface ShippingLabelConstructor {2 new (shipment: Shipment): ShippingLabel;3}45function createLabel(6 LabelClass: ShippingLabelConstructor,7 shipment: Shipment8): ShippingLabel {9 return new LabelClass(shipment);10}1112// Usage13const label = createLabel(USPSLabel, shipment);
Factory Map Pattern
Use a Map for dynamic factory lookup:
typescript1type Carrier = 'usps' | 'fedex' | 'ups';23const labelFactories: Record<Carrier, LabelCreator> = {4 usps: (s) => new USPSLabel(s),5 fedex: (s) => new FedExLabel(s),6 ups: (s) => new UPSLabel(s),7};89function createLabelForCarrier(10 carrier: Carrier,11 shipment: Shipment12): ShippingLabel {13 const factory = labelFactories[carrier];14 return factory(shipment);15}
Async Factory Method
Handle async creation:
typescript1abstract class AsyncLabelFactory {2 abstract createLabel(shipment: Shipment): Promise<ShippingLabel>;34 async processShipment(shipment: Shipment): Promise<void> {5 const label = await this.createLabel(shipment);6 label.print();7 }8}910class FedExLabelFactory extends AsyncLabelFactory {11 async createLabel(shipment: Shipment): Promise<ShippingLabel> {12 // Fetch tracking number from FedEx API13 const tracking = await this.fetchTrackingNumber(shipment);14 return new FedExLabel(shipment, tracking);15 }1617 private async fetchTrackingNumber(shipment: Shipment): Promise<string> {18 // API call simulation19 return `FEDEX${Date.now()}`;20 }21}
Factory with Validation
Add validation to the creation process:
typescript1abstract class ValidatingLabelFactory {2 abstract createLabel(shipment: Shipment): ShippingLabel;34 protected abstract validateShipment(shipment: Shipment): void;56 createValidatedLabel(shipment: Shipment): ShippingLabel {7 this.validateShipment(shipment);8 return this.createLabel(shipment);9 }10}1112class InternationalLabelFactory extends ValidatingLabelFactory {13 createLabel(shipment: Shipment): ShippingLabel {14 return new InternationalLabel(shipment);15 }1617 protected validateShipment(shipment: Shipment): void {18 if (!shipment.customsDeclaration) {19 throw new Error('International shipments require customs declaration');20 }21 if (!shipment.destination.country) {22 throw new Error('Destination country is required');23 }24 }25}
Testing with Factory Method
Factories make testing easier:
typescript1// Test double2class MockLabelFactory extends LabelFactory {3 public lastCreatedLabel: ShippingLabel | null = null;45 createLabel(shipment: Shipment): ShippingLabel {6 const mockLabel: ShippingLabel = {7 trackingNumber: 'MOCK123',8 carrier: 'mock',9 print: vi.fn(),10 };11 this.lastCreatedLabel = mockLabel;12 return mockLabel;13 }14}1516// In tests17describe('ShipmentProcessor', () => {18 it('should create and print label', () => {19 const mockFactory = new MockLabelFactory();20 const processor = new ShipmentProcessor(mockFactory);2122 processor.process(testShipment);2324 expect(mockFactory.lastCreatedLabel?.print).toHaveBeenCalled();25 });26});
Best Practices
- Use interfaces for products - Maximum flexibility
- Consider functional approach - Simpler for small cases
- Combine with Registry - For dynamic factory lookup
- Add validation in factory - Fail fast with clear errors
- Make factories testable - Easy to mock in tests
Summary
| Approach | Use Case |
|---|---|
| Abstract class | Need shared behavior in creator |
| Interface | Maximum flexibility |
| Functional | Simple cases, composition |
| Generic | Type-safe reusable factories |
| Async | Network/IO during creation |
Next: Build a shipping label factory workshop!