Pattern Factory Method
Définissez une fonction de création dont la logique d'instanciation concrète est déférée à l'appelant.
Le Problème
Vous avez une classe Document qui crée des pages. Les documents Word créent des WordPage, les documents PDF créent des PdfPage. L'approche POO : une méthode factory abstraite surchargée par les sous-classes.
abstract class Document {
abstract createPage(): Page;
addPage() {
const page = this.createPage();
this.pages.push(page);
}
}
class WordDocument extends Document {
createPage() { return new WordPage(); }
}
class PdfDocument extends Document {
createPage() { return new PdfPage(); }
}
De l'héritage pour remplacer un seul appel de fonction. Chaque nouveau format implique une nouvelle sous-classe.
La Solution
Passez la factory en paramètre. C'est de la dependency injection — pas besoin d'héritage.
type Page = { type: string; content: string };
// La factory est juste un paramètre — échangez-la librement
function addPage(pages: Page[], createPage: () => Page): Page[] {
return [...pages, createPage()];
}
const wordFactory = (): Page => ({ type: "word", content: "" });
const pdfFactory = (): Page => ({ type: "pdf", content: "" });
let pages: Page[] = [];
pages = addPage(pages, wordFactory); // [{ type: "word", ... }]
pages = addPage(pages, pdfFactory); // [{ type: "word", ... }, { type: "pdf", ... }]
Pas d'héritage. Pas de classes abstraites. Juste passer une fonction.
Cette solution n'utilise pas Pithos. C'est justement le point.
En TypeScript fonctionnel, passer une fonction factory en paramètre est le pattern Factory Method. Eidos exporte une fonction @deprecated createFactoryMethod() qui n'existe que pour vous guider ici.
Démo
Choisissez un format (Word, PDF, HTML, Markdown) et cliquez sur Add Page. La fonction addPage ne sait pas quel type de page elle crée — elle appelle simplement la factory injectée. Changez le format, même fonction, résultat différent.
- factory.ts
- Usage
/**
* Factory Method — Notification Factory.
*
* sendNotification(createNotif) never changes.
* Only the factory parameter changes — that's the whole pattern.
*/
import type { AppNotification, ChannelKey } from "./types";
const SUBJECTS = ["Your order has shipped", "Weekly digest ready", "Password reset requested", "New comment on your post", "Invoice #4821 available", "Deployment succeeded"];
const EMAILS = ["alice@example.com", "bob@example.com", "team@example.com"];
const PHONES = ["+33 6 12 34 56 78", "+1 555 0123", "+44 7911 123456"];
const SLACK_CHANNELS = ["#general", "#engineering", "#alerts", "#deploys"];
let idx = 0;
function pick<T>(arr: T[]): T { return arr[idx % arr.length]; }
function nextSubject(): string { const s = pick(SUBJECTS); idx++; return s; }
function createEmailNotification(): AppNotification {
const subject = nextSubject();
return { id: crypto.randomUUID(), channel: "email", title: subject, fields: [{ label: "To", value: pick(EMAILS) }, { label: "Subject", value: subject }, { label: "Body", value: "Please find the details attached. Best regards." }], sentAt: Date.now() };
}
function createSmsNotification(): AppNotification {
const subject = nextSubject();
return { id: crypto.randomUUID(), channel: "sms", title: subject, fields: [{ label: "To", value: pick(PHONES) }, { label: "Message", value: subject }], sentAt: Date.now() };
}
function createPushNotification(): AppNotification {
const subject = nextSubject();
return { id: crypto.randomUUID(), channel: "push", title: subject, fields: [{ label: "Title", value: subject }, { label: "Badge", value: `${(idx % 9) + 1}` }, { label: "Sound", value: "default" }], sentAt: Date.now() };
}
function createSlackNotification(): AppNotification {
const subject = nextSubject();
return { id: crypto.randomUUID(), channel: "slack", title: subject, fields: [{ label: "Channel", value: pick(SLACK_CHANNELS) }, { label: "Text", value: subject }, { label: "Bot", value: "pithos-bot" }], sentAt: Date.now() };
}
export const factories: Record<ChannelKey, () => AppNotification> = {
email: createEmailNotification,
sms: createSmsNotification,
push: createPushNotification,
slack: createSlackNotification,
};
/** The consumer that NEVER changes */
export function sendNotification(createNotif: () => AppNotification): AppNotification {
return createNotif();
}
export function resetCounter(): void { idx = 0; }
import { useState, useCallback } from "react";
import { sendNotification, factories, resetCounter } from "@/lib/factory";
import type { AppNotification, ChannelKey } from "@/lib/types";
export function useNotificationDemo() {
const [channel, setChannel] = useState<ChannelKey>("email");
const [notifications, setNotifications] = useState<AppNotification[]>([]);
const [lastSent, setLastSent] = useState<string | null>(null);
const handleSend = useCallback(() => {
const notif = sendNotification(factories[channel]);
setNotifications((prev) => [notif, ...prev]);
setLastSent(notif.id);
setTimeout(() => setLastSent(null), 600);
}, [channel]);
const handleReset = useCallback(() => {
setNotifications([]);
setLastSent(null);
resetCounter();
}, []);
return { channel, setChannel, notifications, lastSent, handleSend, handleReset };
}
API
- factoryMethod
@deprecated— utilisez la dependency injection à la place
Liens connexes
- Eidos : Module Design Patterns Les 23 patterns GoF réimaginés pour le TypeScript fonctionnel
- Pourquoi la FP plutôt que la POO ? La philosophie derrière Eidos : pas de classes, pas d'héritage, juste des fonctions et des types
- Zygos Result Encapsulez vos appels factory dans
Resultpour une gestion d'erreurs typée