Abstract Factory Pattern
The Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It's particularly useful when your code needs to work with various families of related products, but you don't want it to depend on the concrete classes of those products.
Problem
Imagine you're creating a UI toolkit that needs to work across multiple operating systems. Each operating system requires different UI components (buttons, checkboxes, etc.) with their own look and feel. You want to ensure that the UI components match the operating system style, but you don't want your code tightly coupled to specific UI component classes.
Or consider a furniture shop that sells furniture sets. You want to ensure that the pieces of furniture you create match each other. You don't want to create a colonial-style sofa with a modern chair or table.
Implementation Example
Here's a basic implementation of the Abstract Factory pattern in JavaScript for UI components:
// Abstract Products
class Button {
render() {
throw new Error("Button.render() must be implemented");
}
onClick() {
throw new Error("Button.onClick() must be implemented");
}
}
class Checkbox {
render() {
throw new Error("Checkbox.render() must be implemented");
}
toggle() {
throw new Error("Checkbox.toggle() must be implemented");
}
}
// Concrete Products for Windows
class WindowsButton extends Button {
render() {
return '<button class="windows-button">Windows Button</button>';
}
onClick() {
return "Windows button clicked";
}
}
class WindowsCheckbox extends Checkbox {
render() {
return '<div class="windows-checkbox"><input type="checkbox"></div>';
}
toggle() {
return "Windows checkbox toggled";
}
}
// Concrete Products for MacOS
class MacOSButton extends Button {
render() {
return '<button class="macos-button">MacOS Button</button>';
}
onClick() {
return "MacOS button clicked";
}
}
class MacOSCheckbox extends Checkbox {
render() {
return '<div class="macos-checkbox"><input type="checkbox"></div>';
}
toggle() {
return "MacOS checkbox toggled";
}
}
// Abstract Factory
class GUIFactory {
createButton() {
throw new Error("GUIFactory.createButton() must be implemented");
}
createCheckbox() {
throw new Error("GUIFactory.createCheckbox() must be implemented");
}
}
// Concrete Factories
class WindowsFactory extends GUIFactory {
createButton() {
return new WindowsButton();
}
createCheckbox() {
return new WindowsCheckbox();
}
}
class MacOSFactory extends GUIFactory {
createButton() {
return new MacOSButton();
}
createCheckbox() {
return new MacOSCheckbox();
}
}
// Client Code
class Application {
constructor(factory) {
this.factory = factory;
this.button = null;
this.checkbox = null;
}
createUI() {
this.button = this.factory.createButton();
this.checkbox = this.factory.createCheckbox();
}
paint() {
return `
<div>
${this.button.render()}
${this.checkbox.render()}
</div>
`;
}
}
// Application configuration
function configureApplication() {
const os = getOperatingSystem(); // This would detect the OS in a real app
let factory;
if (os === "Windows") {
factory = new WindowsFactory();
} else if (os === "MacOS") {
factory = new MacOSFactory();
} else {
throw new Error("Unknown operating system");
}
const app = new Application(factory);
app.createUI();
return app;
}
// In this example, we'll just hardcode the OS
function getOperatingSystem() {
return "Windows";
}
// Run the application
const app = configureApplication();
const ui = app.paint();
Components
- Abstract Factory: Declares interface for creating abstract product objects
- Concrete Factory: Implements the operations to create concrete product objects
- Abstract Product: Declares an interface for a type of product object
- Concrete Product: Defines a product object to be created by the corresponding concrete factory
- Client: Uses only interfaces declared by Abstract Factory and Abstract Product classes
When to Use
- When a system needs to be independent from how its products are created, composed, and represented
- When a system should be configured with one of multiple families of products
- When a family of related product objects is designed to be used together, and you need to enforce this constraint
- When you want to provide a library of products, and you want to reveal just their interfaces, not their implementations
Benefits
- Isolates concrete classes, as the factory encapsulates the responsibility of creating product objects
- Makes exchanging product families easy, as the concrete factory can be changed at runtime
- Promotes consistency among products, as objects within a family are designed to work together
- Follows the Open/Closed Principle: you can introduce new variants of products without breaking existing client code
Real-World Uses
- UI libraries that need to support multiple look-and-feel standards
- Code that must work across multiple database types or platforms
- Frameworks that allow plug-in components with different implementations
- Graphics rendering engines that need to support different rendering modes
- Cross-platform applications that need to adapt to different operating systems
Interactive Demo: Furniture Factory
This demo demonstrates how the Abstract Factory pattern allows you to create families of related objects. Select a furniture style to see how different furniture pieces are created that match the same style.