+TypeScript example
If Dialog chooses themes and draws every product, another theme also changes the dialog. IWidgetFactory selects a related set of products; Dialog only uses the products it receives.
interface IButton { draw(): string; }
interface ICheckbox { draw(): string; }
// Keep theme selection out of the dialog and behind a product-family contract.
interface IWidgetFactory {
button(): IButton;
checkbox(): ICheckbox;
}
class LightButton implements IButton { draw(): string { return "light button"; } }
class LightCheckbox implements ICheckbox { draw(): string { return "light checkbox"; } }
class DarkButton implements IButton { draw(): string { return "dark button"; } }
class DarkCheckbox implements ICheckbox { draw(): string { return "dark checkbox"; } }
class LightFactory implements IWidgetFactory {
button(): IButton { return new LightButton(); }
checkbox(): ICheckbox { return new LightCheckbox(); }
}
class DarkFactory implements IWidgetFactory {
button(): IButton { return new DarkButton(); }
checkbox(): ICheckbox { return new DarkCheckbox(); }
}
// The dialog uses selected products without constructing concrete theme classes.
class Dialog {
constructor(private readonly factory: IWidgetFactory) {}
render(): string {
return this.factory.button().draw() + " + " + this.factory.checkbox().draw();
}
}
for (const factory of [new LightFactory(), new DarkFactory()]) {
console.log(new Dialog(factory).render());
}
light button + light checkbox
dark button + dark checkbox
The TypeScript interfaces express the same roles as the C# interfaces. Each factory implementation remains responsible for returning a matching product family.