iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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 …; }

Discussion

Loading…