Abstract Factory Pattern
Provide a single entry point for creating families of related objects without coupling to their concrete types.
The Problemβ
You're building a cross-platform UI kit. Each platform (iOS, Android, Web) needs its own button, input, and modal. But the form logic shouldn't care which platform it's running on.
The naive approach:
function createButton(platform: string, label: string) {
if (platform === "ios") return { type: "UIButton", label, style: "rounded" };
if (platform === "android") return { type: "MaterialButton", label, style: "elevated" };
if (platform === "web") return { type: "button", label, style: "outlined" };
}
function createInput(platform: string, placeholder: string) {
if (platform === "ios") return { type: "UITextField", placeholder };
// ... same pattern repeated for every component
}
Platform checks in every factory. Easy to mix iOS buttons with Android inputs. Adding a new platform means updating every function.
The Solutionβ
Group related factories into families. Pick the platform once, get a consistent kit:
import { createAbstractFactory } from "@pithos/core/eidos/abstract-factory/abstract-factory";
type UIKit = {
button: (label: string) => UIElement;
input: (placeholder: string) => UIElement;
modal: (title: string, content: string) => UIElement;
};
const uiFactory = createAbstractFactory<"ios" | "android" | "web", UIKit>({
ios: () => ({
button: (label) => ({ tag: "UIButton", label, radius: 12 }),
input: (ph) => ({ tag: "UITextField", placeholder: ph, border: "none" }),
modal: (title, content) => ({ tag: "UIAlert", title, content }),
}),
android: () => ({
button: (label) => ({ tag: "MaterialButton", label, elevation: 4 }),
input: (ph) => ({ tag: "TextInput", placeholder: ph, underline: true }),
modal: (title, content) => ({ tag: "AlertDialog", title, content }),
}),
web: () => ({
button: (label) => ({ tag: "button", label, className: "btn" }),
input: (ph) => ({ tag: "input", placeholder: ph, className: "form-input" }),
modal: (title, content) => ({ tag: "dialog", title, content }),
}),
});
// Consumer code: platform-agnostic
const ui = uiFactory.create("ios");
const loginForm = [
ui.input("Email"),
ui.input("Password"),
ui.button("Sign In"),
];
// All components guaranteed to be iOS. Switch to "android" and everything re-skins.
Platform selected once. All components from the same family. No mixing possible.
Live Demoβ
A mini phone screen with a form. Switch between iOS, Android, and Web and watch the entire UI re-skin at once: buttons, inputs, modals, navigation. The consumer code calls factory.createButton() without knowing which platform is active.
- factory.ts
- Usage
/**
* Abstract Factory for cross-platform UI kits.
*
* One factory.create() call returns a full UIKit (button, input, modal, nav, theme)
* styled for the selected platform. Consumer code never changes.
*/
import { createAbstractFactory } from "@pithos/core/eidos/abstract-factory/abstract-factory";
import { PLATFORM_FAMILIES } from "@/data/platforms";
import type { Platform, UIKit } from "./types";
export const uiFactory = createAbstractFactory<Platform, UIKit>(PLATFORM_FAMILIES);
import { useState, useMemo, useCallback } from "react";
import { uiFactory } from "@/lib/factory";
import type { Platform } from "@/lib/types";
export function usePhonePreview() {
const [platform, setPlatform] = useState<Platform>("ios");
const [modalOpen, setModalOpen] = useState(false);
const [formValues, setFormValues] = useState({ name: "", email: "" });
const kit = useMemo(() => uiFactory.create(platform), [platform]);
const handleSubmit = useCallback(() => setModalOpen(true), []);
const handleCloseModal = useCallback(() => {
setModalOpen(false);
setFormValues({ name: "", email: "" });
}, []);
const updateField = useCallback((field: "name" | "email", value: string) => {
setFormValues((prev) => ({ ...prev, [field]: value }));
}, []);
return {
platform,
setPlatform,
kit,
modalOpen,
formValues,
updateField,
handleSubmit,
handleCloseModal,
};
}
Real-World Analogyβ
A furniture store with style collections: Modern, Victorian, Art Deco. When you pick "Modern", you get a modern chair, modern table, modern lamp: all designed to work together. You don't mix Victorian chairs with Art Deco tables.
When to Use Itβ
- Create families of related objects that must be used together
- Support multiple platforms, themes, or configurations
- Ensure visual/behavioral consistency across related components
- Swap entire families at runtime (theme switching, platform detection)
When NOT to Use Itβ
If you only have one "family" or your objects aren't related, a simple factory function is enough. Abstract Factory adds indirection that only pays off when you have multiple interchangeable families.
APIβ
- createAbstractFactory β Build a factory that produces families of related objects