Skip to main content

Factory Method Pattern

Define a creation function whose concrete instantiation logic is deferred to the caller.


The Problem​

You have a Document class that creates pages. Word documents create WordPage, PDF documents create PdfPage. The OOP approach: an abstract factory method overridden by subclasses.

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(); }
}

Inheritance to swap a single function call. Every new format means a new subclass.


The Solution​

Pass the factory as a parameter. That's dependency injection β€” no inheritance needed.

type Page = { type: string; content: string };

// The factory is just a parameter β€” swap it freely
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", ... }]

No inheritance. No abstract classes. Just pass a function.

Absorbed by the Language

This solution doesn't use Pithos. That's the point.

In functional TypeScript, passing a factory function as a parameter is the Factory Method pattern. Eidos exports a @deprecated createFactoryMethod() function that exists only to guide you here.


Live Demo​

Pick a format (Word, PDF, HTML, Markdown) and click Add Page. The addPage function doesn't know which page type it creates β€” it just calls the injected factory. Swap the format, same function, different output.

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 β€” use dependency injection instead

Related