SOLUTION: Challenge 2 - Factory with Type Safety ================================================= Challenge: Create a factory that builds different notification strategies (Email, SMS, Push). Use generics to ensure type safety across implementations. --- SOLUTION: // Generic notification interface interface Notification { send(recipient: string, payload: T): Promise; validate(payload: T): boolean; } // Email notification interface EmailPayload { subject: string; body: string; html?: boolean; } class EmailNotification implements Notification { async send(recipient: string, payload: EmailPayload): Promise { console.log(`📧 Sending email to ${recipient}`); console.log(` Subject: ${payload.subject}`); console.log(` Body: ${payload.body}`); } validate(payload: EmailPayload): boolean { return payload.subject.length > 0 && payload.body.length > 0; } } // SMS notification interface SMSPayload { message: string; priority: "low" | "normal" | "high"; } class SMSNotification implements Notification { async send(recipient: string, payload: SMSPayload): Promise { console.log(`📱 Sending SMS to ${recipient}`); console.log(` Message: ${payload.message}`); console.log(` Priority: ${payload.priority}`); } validate(payload: SMSPayload): boolean { return payload.message.length > 0 && payload.message.length <= 160; } } // Push notification interface PushPayload { title: string; message: string; actionUrl?: string; badge?: number; } class PushNotification implements Notification { async send(recipient: string, payload: PushPayload): Promise { console.log(`🔔 Sending push notification to ${recipient}`); console.log(` Title: ${payload.title}`); console.log(` Message: ${payload.message}`); if (payload.actionUrl) console.log(` Action: ${payload.actionUrl}`); } validate(payload: PushPayload): boolean { return payload.title.length > 0 && payload.message.length > 0; } } --- // FACTORY: Type-safe creation type NotificationType = "email" | "sms" | "push"; // Map notification types to their payload types type NotificationPayloadMap = { email: EmailPayload; sms: SMSPayload; push: PushPayload; }; // Factory function with generics function createNotification( type: T ): Notification { switch (type) { case "email": return new EmailNotification() as Notification; case "sms": return new SMSNotification() as Notification; case "push": return new PushNotification() as Notification; default: const _exhaustive: never = type; throw new Error(`Unknown notification type: ${_exhaustive}`); } } --- // USAGE: Type-safe, can't mix payload types! async function sendNotifications() { // Email: correctly typed const emailNotif = createNotification("email"); const emailPayload: EmailPayload = { subject: "Welcome!", body: "Thank you for signing up." }; if (emailNotif.validate(emailPayload)) { await emailNotif.send("user@example.com", emailPayload); } // SMS: correctly typed const smsNotif = createNotification("sms"); const smsPayload: SMSPayload = { message: "Your code is 123456", priority: "high" }; if (smsNotif.validate(smsPayload)) { await smsNotif.send("+1234567890", smsPayload); } // Push: correctly typed const pushNotif = createNotification("push"); const pushPayload: PushPayload = { title: "New Message", message: "You have a new message from Alice", actionUrl: "/messages/alice" }; if (pushNotif.validate(pushPayload)) { await pushNotif.send("user-device-id", pushPayload); } // This would error (compile-time): // const emailNotif = createNotification("email"); // const wrongPayload: SMSPayload = { message: "Hi" }; // await emailNotif.send("user@example.com", wrongPayload); // ❌ Type error! } sendNotifications(); --- ADVANCED: Generic Sender Class // Reusable sender with generics class NotificationSender { private notifier: Notification; constructor(type: T) { this.notifier = createNotification(type); } async send( recipient: string, payload: NotificationPayloadMap[T] ): Promise { if (!this.notifier.validate(payload)) { throw new Error("Invalid payload"); } await this.notifier.send(recipient, payload); } canHandle(type: NotificationType): boolean { return type === this.notifier.constructor.name.toLowerCase(); } } // Usage: const emailSender = new NotificationSender("email"); await emailSender.send("user@example.com", { subject: "Hello", body: "This is an email" }); --- EXPLANATION: FACTORY PATTERN: function createNotification(type: T): Notification<...> - Takes a type identifier - Returns correct implementation - Hides creation details from caller GENERIC MAPPING: type NotificationPayloadMap = { email: EmailPayload; sms: SMSPayload; push: PushPayload; }; Maps notification types to their payload types. Enables NotificationPayloadMap[T] to give the right type for each notification. TYPE SAFETY: When you create a notification: const emailNotif = createNotification("email"); // emailNotif is Notification You can only pass EmailPayload to send(): await emailNotif.send("email@example.com", { subject: "...", body: "..." }); Passing SMSPayload would be a compile error! BENEFITS: 1. Single entry point: all notifications created through factory 2. Type safety: can't accidentally pass wrong payload type 3. Extensibility: add new notification types without changing existing code 4. Validation: each type knows how to validate its payload 5. Consistency: all notifications follow the same interface --- REAL-WORLD USAGE: This pattern is used in: - AWS SDK: create clients (EC2, S3, etc.) - React: component factories - ORMs: query builder factories - Database drivers: connection factories Combining factory pattern with generics gives the best of both worlds: - Flexibility of factories (multiple implementations) - Safety of generics (type constraints)