Every site that constructs a UI widget switches on theme — and adding a new theme means hunting every switch and adding a new branch in lockstep. One missed branch silently produces a half-themed UI that ships looking 'mostly right' until a designer or QA notices. The team's verification cost and comprehension cost both compound across every switch site.
A factory interface that produces a coherent family of widgets, with concrete factories enforcing family-internal consistency. Client code receives a factory and asks for products; it never names the concrete class and cannot mix widgets across themes by accident, supporting separation of concerns between client logic and family selection.
Before the pattern
function renderToolbar(theme) {let button;let textField;if (theme === 'light') {button = new LightButton();textField = new LightTextField();} else if (theme === 'dark') {button = new DarkButton();textField = new DarkTextField();} else {throw new Error(`unknown theme: ${theme}`);}return [button, textField];}
After the pattern
class LightWidgetFactory {createButton() { return new LightButton(); }createTextField() { return new LightTextField(); }}class DarkWidgetFactory {createButton() { return new DarkButton(); }createTextField() { return new DarkTextField(); }}function renderToolbar(factory) {return [factory.createButton(), factory.createTextField()];}
Theme dispatch is Shotgun Surgery at every construction site. The type system has no way to enforce that all widgets in one render path belong to the same theme — that invariant lives in convention and reviewer attention, both of which erode under deadline pressure. The blast radius of any theme edit stretches into every render path.
The factory interface and concrete subfactories grow in lockstep with the product set. Every new product method must be implemented across every concrete factory. Adding one product becomes a one-place-times-N change instead of a one-place change, and the team's maintenance cost per product addition multiplies with the number of factories.
Client code reads as `factory.createButton(); factory.createTextField()` regardless of theme. New theme = one new concrete factory; products in one render path are family-coherent by construction; the linter or the type system catches missing factory methods at compile time, not at user-visible runtime. The team's enhancement cost drops because new themes add one file rather than N call-site edits.
The factory interface accretes product methods over time and becomes a god interface. Sub-families with optional products (e.g., one theme has a DateRangePicker, another doesn't) force either NotImplemented throws or interface segregation — both costlier than the original switch and harder to undo. The cure adds accidental complexity when the interface stops describing one coherent family.