Abstract Factory Pattern
Abstract Factory is creational design pattern that provides interface for creating families of interrelated objects without specifying concrete classes. This pattern is especially useful when your code must work with different "families" of objects, but remain independent from concrete implementations.
Idea:
"Abstract Factory" helps create products belonging to one family through common interface, hiding implementation details.
When to Apply "Abstract Factory"?
- When program should have several "families" of products that are used together. For example, different themes (Theme) in UI (light, dark) or different engines (2D/3D).
- When need to create objects depending on specific configuration or runtime environment (e.g., Web vs. Mobile), but code shouldn't depend on details of this configuration.
- When need to hide from client logic of choosing concrete implementation, so it can be easily changed without affecting rest of code.
How It Works
1. Defining Product Families
First we define types of products that should be created together. For example: Button, Checkbox, Modal for different platforms or themes.
2. Creating Abstract Interfaces
Each product family has abstract interfaces. For example: Button with render() method and Checkbox with toggle() method.
3. Implementing Concrete Products
For each platform (or theme) concrete classes are created implementing these interfaces. For example: MacButton, MacCheckbox and WinButton, WinCheckbox.
4. Abstract Factory
Abstract factory defines methods for creating products (e.g., createButton(), createCheckbox()) and returns abstract types. Concrete factories (MacFactory, WinFactory) override these methods, creating corresponding objects.
5. Client Code
Client works only with abstract factory and abstract products, not binding to concrete implementations. Factory choice (Mac vs. Win) can be configured without changing rest of code.
Example (TypeScript)
// 1. Abstract product interfaces
interface Button {
render(): void;
}
interface Checkbox {
toggle(): void;
}
// 2. Concrete products
class MacButton implements Button {
render(): void {
console.log("Mac button rendered");
}
}
class WinButton implements Button {
render(): void {
console.log("Windows button rendered");
}
}
class MacCheckbox implements Checkbox {
toggle(): void {
console.log("Mac checkbox toggled");
}
}
class WinCheckbox implements Checkbox {
toggle(): void {
console.log("Windows checkbox toggled");
}
}
// 3. Abstract factory
interface GUIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
// 4. Concrete factories
class MacFactory implements GUIFactory {
createButton(): Button {
return new MacButton();
}
createCheckbox(): Checkbox {
return new MacCheckbox();
}
}
class WinFactory implements GUIFactory {
createButton(): Button {
return new WinButton();
}
createCheckbox(): Checkbox {
return new WinCheckbox();
}
}
// 5. Client code
function runApp(factory: GUIFactory) {
const button = factory.createButton();
button.render();
const checkbox = factory.createCheckbox();
checkbox.toggle();
}
// Can easily substitute factory
// Mac UI
runApp(new MacFactory());
// Windows UI
runApp(new WinFactory());
Source
Abstract Factory is excellent for large systems where there are several variants of same "family" of products. It makes code flexible, allowing to substitute entire sets of objects without changing main logic. However, due to increased number of classes this pattern may be excessive if you only occasionally need to switch between implementations or manage small number of entities.
Read more about Abstract Factory here.
- refactoring.guru - patterns and refactoring
- patterns.dev - patterns