Skip to article
ALGORITHMICSPatterns
Patterns5 min read

Abstract Factory

Whole families that must match — and the cost of adding one more kind of thing.


A widget toolkit has two themes. Every button, input and panel comes in both, and mixing them looks broken.

Factory Method defers one choice. This defers a whole coordinated set, and the coordination is the entire reason it exists.

The pattern

One object with a creation method per product, and one implementation per family.

interface WidgetKit {
createButton(): Button;
createInput(): Input;
createPanel(): Panel;
}
class DarkKit implements WidgetKit {
createButton() { return new DarkButton(); }
createInput() { return new DarkInput(); }
createPanel() { return new DarkPanel(); }
}

Application code takes a WidgetKit and never names a concrete class:

Theme factory
const kit = new DarkKit();
const button = kit.createButton();
const input  = kit.createInput();
const panel  = kit.createPanel();
Input Panel

One factory, one family — you cannot get a dark button next to a light input.

The trade it makes, and it is a real one

Adding a family is easy: one new class, nothing else changes.

Adding a product is not: createTooltip() goes on the interface and every existing kit must implement it. Two families is two edits; ten is ten, in ten files, and the compiler will at least tell you about all of them.

In this language, it can be an object

The interface has no state and no behaviour beyond construction, so:

type WidgetKit = {
createButton: () => Button;
createInput: () => Input;
createPanel: () => Panel;
};
const darkKit: WidgetKit = {
createButton: () => new DarkButton(),
createInput: () => new DarkInput(),
createPanel: () => new DarkPanel(),
};

Same guarantee, no classes. TypeScript will still refuse a kit that forgets a method, which is the part that was doing the work.

Where you have met it

DocumentBuilderFactory and friends in Java’s XML stack. Cross-platform UI toolkits. Database drivers, where a connection creates statements and result sets that must all belong to the same dialect — that last one is the clearest real example, because a Postgres Connection handing you a MySQL PreparedStatement would be nonsense.