One-line Definition
Encapsulates the creation of related object families so clients depend on abstractions instead of concrete groups.
Problem
Suppose your application generates UI components for both Windows and MacOS. A Windows button and a Mac checkbox don't mix well visually or functionally. If client code instantiates these directly, it's easy to accidentally mix families or scatter OS-specific if checks everywhere. Abstract Factory solves this by forcing the creation of a consistent set of products.
Real-world Analogy
Think of a furniture store that sells matching sets: a "Modern" collection and a "Victorian" collection. When you pick "Modern," you get a modern chair, modern table, and modern sofa — all guaranteed to match. You never mix a Victorian chair with a modern table. The abstract factory is the catalog that ensures you always get a consistent set.
Structure
- Client
- Requests objects from the factory and works only with abstract interfaces, never concrete families.
- AbstractFactory
- Interface declaring creation methods for each product in the family.
- ConcreteFactory
- Implements the AbstractFactory to produce a specific family of products.
- AbstractProduct
- Interface for each kind of product.
- ConcreteProduct
- The actual product implementations.
Diagram
classDiagram
class Client {
}
class GUIFactory {
<<interface>>
+createButton(): Button
+createCheckbox(): Checkbox
}
class WindowsFactory {
+createButton(): Button
+createCheckbox(): Checkbox
}
class MacFactory {
+createButton(): Button
+createCheckbox(): Checkbox
}
Client --> GUIFactory
WindowsFactory ..|> GUIFactory
MacFactory ..|> GUIFactory
Code Walkthrough
Notice that the Application (client) depends only on GUIFactory, Button, and Checkbox. The concrete factory ensures the app always gets a matching family of UI components.
interface Button {
void render();
}
interface Checkbox {
void render();
}
class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Rendering Windows button");
}
}
class MacButton implements Button {
@Override
public void render() {
System.out.println("Rendering Mac button");
}
}
class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Windows checkbox");
}
}
class MacCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering Mac checkbox");
}
}
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
class Application {
private final Button button;
private final Checkbox checkbox;
public Application(GUIFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
public void renderUI() {
button.render();
checkbox.render();
}
}
class Main {
public static void main(String[] args) {
String os = System.getProperty("os.name").toLowerCase();
GUIFactory factory;
if (os.contains("win")) {
factory = new WindowsFactory();
} else {
factory = new MacFactory();
}
Application app = new Application(factory);
app.renderUI();
}
}
Bad vs Good
Bad Approach
Problems
- Client code is littered with OS checks for every component.
- Easy to accidentally mix a Windows button with a Mac checkbox.
class Application {
public void renderUI(String os) {
if (os.equals("windows")) {
System.out.println("Rendering Windows button");
System.out.println("Rendering Windows checkbox");
} else if (os.equals("mac")) {
System.out.println("Rendering Mac button");
System.out.println("Rendering Mac checkbox");
}
}
}
class Main {
public static void main(String[] args) {
Application app = new Application();
app.renderUI("windows");
}
}
Better Approach
Improvements
- Each factory guarantees a consistent product family.
- New platforms are added by extending the factory, keeping business logic unchanged.
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
class WindowsFactory implements GUIFactory {
@Override
public Button createButton() { return new WindowsButton(); }
@Override
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
class MacFactory implements GUIFactory {
@Override
public Button createButton() { return new MacButton(); }
@Override
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
class Application {
private final Button button;
private final Checkbox checkbox;
public Application(GUIFactory factory) {
this.button = factory.createButton();
this.checkbox = factory.createCheckbox();
}
public void renderUI() {
button.render();
checkbox.render();
}
}
Pros vs Cons
| Pros | Cons |
|---|---|
| Guarantees product family consistency | High class count — every product needs an interface and multiple implementations |
| Decouples client code from concrete types | Adding a new product type (e.g., Slider) requires updating every factory |
| Swapping product families is a one-line change | Overkill for single product families |
| Centralizes family creation logic | Increases upfront design complexity |
| Prevents accidental mixing of incompatible objects | Hard to navigate for new team members |
When to Use
- Your system needs to work with multiple families of products.
- Products in a family must be used together to function correctly.
- You want to enforce consistency across a group of instantiated objects.
- You're building a cross-platform application or theme system.
When Not to Use
- You only have one product family.
- Products do not need to be rigorously grouped.
- The product families change frequently, requiring constant updates to all factories.
- A simpler Factory Method suffices.
Real-world Examples
javax.xml.parsers.DocumentBuilderFactoryjavax.xml.transform.TransformerFactoryjavax.xml.xpath.XPathFactory- GUI toolkits (creating buttons, text fields, etc. matching a specific LookAndFeel)
Key Takeaway
The Abstract Factory Pattern ensures that related objects are always created together as a consistent family, making it impossible to mix incompatible components. Use it when your system supports multiple "themes" or "platforms," but avoid it if you only have a single product family, as a simpler Factory Method is sufficient.