CreationalTypeScriptverifiedVerified
Factory Method Pattern in TypeScript
Defines an interface for creating an object but lets subclasses decide which class to instantiate. Defers instantiation to subclasses.
How to Implement the Factory Method Pattern in TypeScript
1Step 1: Define the product interface
interface Transport {
deliver(cargo: string): string;
}2Step 2: Implement concrete products
class Truck implements Transport {
deliver(cargo: string): string {
return `Delivering "${cargo}" by road in a truck`;
}
}
class Ship implements Transport {
deliver(cargo: string): string {
return `Delivering "${cargo}" by sea in a ship`;
}
}3Step 3: Create the abstract creator with the factory method
abstract class Logistics {
abstract createTransport(): Transport;
planDelivery(cargo: string): string {
const transport = this.createTransport();
return transport.deliver(cargo);
}
}4Step 4: Wire up concrete creators and test
class RoadLogistics extends Logistics {
createTransport(): Transport {
return new Truck();
}
}
class SeaLogistics extends Logistics {
createTransport(): Transport {
return new Ship();
}
}
// Usage
const logistics: Logistics = new RoadLogistics();
console.log(logistics.planDelivery("Electronics"));
const seaLogistics: Logistics = new SeaLogistics();
console.log(seaLogistics.planDelivery("Grain"));// ── Notification Factory ──────────────────────────────────────────
interface NotificationResult {
success: boolean;
messageId: string;
timestamp: number;
}
interface Notification {
readonly channel: string;
send(to: string, subject: string, body: string): Promise<NotificationResult>;
validate(to: string): boolean;
}
class EmailNotification implements Notification {
readonly channel = "email";
validate(to: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(to);
}
async send(to: string, subject: string, body: string): Promise<NotificationResult> {
if (!this.validate(to)) throw new Error(`Invalid email: ${to}`);
return { success: true, messageId: `email_${Date.now()}`, timestamp: Date.now() };
}
}
class SmsNotification implements Notification {
readonly channel = "sms";
validate(to: string): boolean {
return /^\+[1-9]\d{6,14}$/.test(to);
}
async send(to: string, subject: string, body: string): Promise<NotificationResult> {
if (!this.validate(to)) throw new Error(`Invalid phone: ${to}`);
return { success: true, messageId: `sms_${Date.now()}`, timestamp: Date.now() };
}
}
// Creator
abstract class NotificationService {
protected abstract createNotification(): Notification;
async notify(to: string, subject: string, body: string): Promise<NotificationResult> {
const notification = this.createNotification();
if (!notification.validate(to)) {
throw new Error(`Validation failed for ${notification.channel}: ${to}`);
}
return notification.send(to, subject, body);
}
}
class EmailService extends NotificationService {
protected createNotification(): Notification {
return new EmailNotification();
}
}
class SmsService extends NotificationService {
protected createNotification(): Notification {
return new SmsNotification();
}
}
// Registry for runtime selection
const serviceRegistry: Record<string, () => NotificationService> = {
email: () => new EmailService(),
sms: () => new SmsService(),
};
function getNotificationService(channel: string): NotificationService {
const factory = serviceRegistry[channel];
if (!factory) {
throw new Error(`Unknown channel "${channel}". Available: ${Object.keys(serviceRegistry).join(", ")}`);
}
return factory();
}
export { getNotificationService, EmailService, SmsService, type Notification, type NotificationResult };Factory Method Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Factory Method Pattern in the Real World
“Think of a logistics company that ships packages. The headquarters defines the shipping process but doesn’t decide the vehicle. Regional offices (subclasses) choose whether to use trucks, ships, or drones based on local conditions. The headquarters just says ‘get me a transport’ and the regional office delivers the right one.”