Factory Method
The Factory pattern centralises object creation. Instead of new ConcreteX() sprinkled everywhere, you call Factory.create(kind) — one place to change for new types, conditional construction, or replacing with stubs in tests.
Simple factory, factory method, abstract factory
EXAMPLE
// 1) Simple factory — one function, one switch
class UserNotifier { send(msg) { /* email */ } }
class AdminNotifier { send(msg) { /* email + Slack + page */ } }
class GuestNotifier { send(_) { /* no-op */ } }
function makeNotifier(user) {
switch (user.role) {
case 'admin': return new AdminNotifier(user);
case 'guest': return new GuestNotifier(user);
default: return new UserNotifier(user);
}
}
const notifier = makeNotifier(currentUser);
notifier.send('Order shipped');
// 2) Factory method — subclass decides which concrete class
class Document {
save(data) {
const writer = this.createWriter();
writer.write(data);
}
createWriter() { throw new Error('must override'); }
}
class PDFDocument extends Document { createWriter() { return new PDFWriter(); } }
class HTMLDocument extends Document { createWriter() { return new HTMLWriter(); } }
class MDDocument extends Document { createWriter() { return new MDWriter(); } }
// 3) Abstract factory — make families of related objects
interface UIFactory {
createButton(): Button;
createInput(): Input;
createCard(): Card;
}
class IOSUIFactory implements UIFactory {
createButton() { return new IOSButton(); }
createInput() { return new IOSInput(); }
createCard() { return new IOSCard(); }
}
class MaterialUIFactory implements UIFactory {
createButton() { return new MaterialButton(); }
createInput() { return new MaterialInput(); }
createCard() { return new MaterialCard(); }
}
function renderPage(ui: UIFactory) {
return [ui.createCard(), ui.createInput(), ui.createButton()];
}
// renderPage doesn't know iOS from Material — the factory carries the choice.
// 4) Static factory method on the class
class Color {
private constructor(public r: number, public g: number, public b: number) {}
static fromHex(hex: string): Color { /* parse */ return new Color(...); }
static fromRgb(r: number, g: number, b: number) { return new Color(r, g, b); }
static fromHsl(h: number, s: number, l: number) { /* convert */ return new Color(...); }
}
// Color.fromHex('#0ea5e9');
// Color.fromHsl(190, 90, 50);
// 5) Builder + factory — multi-step construction
class HttpRequest {
private constructor(
public url: string,
public method: string,
public headers: Record<string, string>,
public body?: string,
) {}
static builder(url: string) {
let method = 'GET';
let headers: Record<string, string> = {};
let body: string | undefined;
return {
method(m: string) { method = m; return this; },
header(k: string, v: string) { headers[k] = v; return this; },
json(payload: unknown) {
headers['content-type'] = 'application/json';
body = JSON.stringify(payload);
method = 'POST';
return this;
},
build() { return new HttpRequest(url, method, headers, body); },
};
}
}
const req = HttpRequest.builder('/api/x')
.header('Authorization', `Bearer ${token}`)
.json({ a: 1 })
.build();
// 6) Dependency injection containers — the runtime factory
// Angular, NestJS, Spring, .NET DI: registered factories produce instances on demand.
// You declare you need a `Logger`; the container constructs the right impl.
// 7) When NOT to use a factory
// • One class with one constructor — just call new
// • No conditional construction — no factory needed
// • Factory just delegates to new — same coupling, more files
// 8) Why factories matter
// • Single point to change construction (e.g., add caching, add logging wrapper)
// • Test seams — swap real impl for a stub via the factory
// • Object creation depends on runtime data (feature flag, user role, config)
// 9) Common refactor — extract a factory when you notice
// • A `new X` appearing in 5+ places, each in different files
// • Conditional construction inside business code (`if (env === 'prod') new RealX else new FakeX`)
// • Construction needs many parameters that 90% of callers fill the same way
Why it matters
Reach for a factory the moment construction has logic — conditional types, lazy init, decoration. Otherwise it’s noise. The goal is hiding the “how” so callers can ignore it.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
class Logger {
static create(kind) {
return kind === 'json' ? new JsonLogger() : new TextLogger();
}
}
const log = Logger.create('json');
Try it Yourself »
Exercise
Static method that returns an object.
static
(kind) { return new …; }
Six letters.
Discussion
Loading…