Aller au contenu principal

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.

Absorbé par le Langage

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.

Code
/**
* 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; }
Result

API

  • factoryMethod @deprecated — utilisez la dependency injection à la place

Liens connexes