Provide an interface for creating families of related or dependent objects without specifying concrete classes.
IntermediatePattern #03 of 23
01 — Intent
What is the Abstract Factory Pattern?
Abstract Factory provides an interface for creating families of related objects. Where Factory Method creates one product, Abstract Factory creates an entire suite — a "kit" of related products that are designed to work together.
The key constraint is that all products created by one factory are compatible with each other, but incompatible with products from a different factory.
Real-world analogy: IKEA furniture collections — if you buy from the "MALM" collection, every piece (bed, dresser, nightstand) matches. Mixing with "HEMNES" breaks the aesthetic. The collection is your Abstract Factory; each piece is a product family.
02 — Problem
The Problem It Solves
Consider a cross-platform UI toolkit. You need Buttons, Checkboxes, and TextFields — but each must be platform-specific (Windows or Mac). If you create them independently, you risk mixing Windows buttons with Mac checkboxes, which breaks visual consistency.
You need to guarantee that all UI components created for Windows come from one family, and all Mac components from another — with no cross-contamination.
03 — Solution
Participants
AbstractFactory
Interface declaring creation methods for each distinct product in the family (e.g., createButton(), createCheckbox()).
ConcreteFactory
Implements all creation methods to produce a specific family of products (e.g., WindowsFactory, MacFactory).
AbstractProduct
Interface for each type of product (Button, Checkbox). All concrete products of that type implement this.
ConcreteProduct
Specific implementation of a product (WindowsButton, MacButton). Products within a family are designed to work together.
Client
Uses only AbstractFactory and AbstractProduct interfaces — never knows about concrete classes.
04 — Structure
Visual Flow Diagram
05 — Implementation
Java Code Example
Java
// Abstract Product interfacespublic interfaceButton { voidpaint(); }
public interfaceCheckbox { voidpaint(); }
// Windows familypublic classWindowsButtonimplementsButton {
public voidpaint() { System.out.println("Windows Button"); }
}
public classWindowsCheckboximplementsCheckbox {
public voidpaint() { System.out.println("Windows Checkbox"); }
}
// Mac familypublic classMacButtonimplementsButton {
public voidpaint() { System.out.println("Mac Button"); }
}
public classMacCheckboximplementsCheckbox {
public voidpaint() { System.out.println("Mac Checkbox"); }
}
// Abstract Factory interfacepublic interfaceGUIFactory {
ButtoncreateButton();
CheckboxcreateCheckbox();
}
// Concrete Factoriespublic classWindowsFactoryimplementsGUIFactory {
publicButtoncreateButton() { return newWindowsButton(); }
publicCheckboxcreateCheckbox() { return newWindowsCheckbox(); }
}
public classMacFactoryimplementsGUIFactory {
publicButtoncreateButton() { return newMacButton(); }
publicCheckboxcreateCheckbox() { return newMacCheckbox(); }
}
// Client — depends only on interfacespublic classApplication {
privateButton button;
privateCheckbox checkbox;
publicApplication(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public voidpaint() { button.paint(); checkbox.paint(); }
}
// Bootstrap — only place that knows concrete factorypublic classMain {
public static voidmain(String[] args) {
GUIFactory factory = getFactory(System.getProperty("os.name"));
newApplication(factory).paint();
}
staticGUIFactorygetFactory(String os) {
return os.contains("Windows") ? newWindowsFactory() : newMacFactory();
}
}
06 — Applicability
When to Use / Avoid
✓ Use When
System needs to be independent of how its products are created
System works with multiple families of products that must stay compatible
You want to enforce constraints between related products
You're building a cross-platform library or framework
✕ Avoid When
Adding new product types to the family is frequent — requires changing the interface
javax.xml.parsers.DocumentBuilderFactory — XML parser family
Spring's PlatformTransactionManager — transaction families per DB provider
JDBC — each driver provides a family of Connection, Statement, ResultSet objects
08 — Trade-offs
Pros & Cons
Pros
Guarantees product family compatibility
Isolates concrete classes from client code
Easy to swap entire product families (just change the factory)
Open/Closed for new families — add factory without changing client
Cons
Adding new product types to a family requires changing the AbstractFactory interface — violates OCP
Many interfaces and classes — significant code increase
09 — Break & Prevent
How Abstract Factory Can Be Broken
⚠ Attack Vectors
Cross-family mixing: Nothing stops a client from getting a button from WindowsFactory and a checkbox from MacFactory — producing an inconsistent UI
Casting to concrete type: Client casts Button back to WindowsButton and calls platform-specific methods, re-coupling to a concrete class
Extending the AbstractFactory interface: Adding a new product type (e.g., createScrollbar()) to the interface forces all existing ConcreteFactories to change — violates OCP
Runtime factory switching mid-session: Swapping from WindowsFactory to MacFactory after some products are created leaves a mix of Windows and Mac components alive simultaneously
✓ Prevention
Enforce factory per session: Inject the factory once at startup and make it final/immutable. Document that the factory should not be changed at runtime
Hide concrete product classes: Keep all ConcreteProduct classes package-private; clients can only see the product interfaces — impossible to downcast
Extend via new factory, not new method: Instead of adding methods to the AbstractFactory, create a new extended interface and factory class so existing factories are unaffected
Factory registry: Route all factory lookups through a central registry that enforces one factory choice per context, preventing ad-hoc mixing
Java — Break & Fix
// ❌ BREAKING — cross-family mixingGUIFactory winFactory = newWindowsFactory();
GUIFactory macFactory = newMacFactory();
// Nothing in the type system prevents this!Button btn = winFactory.createButton(); // WindowsCheckbox checkbox = macFactory.createCheckbox(); // Mac — mismatch!// ✅ FIX — inject factory once, make it immutablepublic classApplication {
private finalGUIFactory factory; // set once, never changedpublicApplication(GUIFactory factory) { this.factory = factory; }
// All UI components created from single factory — guaranteed compatible
}
// ❌ BREAKING — downcasting defeats abstractionButton btn = factory.createButton();
((WindowsButton) btn).setWindowsSpecificStyle(); // coupled again!// ✅ FIX — keep concrete products package-private// WindowsButton is in package com.ui.windows (not exported)// Client package can only see com.ui.Button interface
10 — Related Patterns
How Other Patterns Relate
Factory Method
Extends
Abstract Factory is often implemented using Factory Methods internally. Factory Method creates one product; Abstract Factory creates a whole coordinated family.
Builder
Often Confused
Both abstract the creation process, but Builder focuses on constructing a single complex object step-by-step. Abstract Factory creates multiple simple related objects in one shot.
Prototype
Often Used Together
A ConcreteFactory can store prototypes and clone them to create products, combining both patterns to reduce subclassing.
Singleton
Often Used Together
Concrete Factory classes are often implemented as Singletons — only one factory per family is usually needed in the application.
11 — Quick Recap
Interview Cheat Sheet
What: Creates families of related objects — all from one factory, all guaranteed to be compatible with each other.
How: Interface with one creation method per product type. Multiple ConcreteFactories each implement the full interface for their family. Client uses only the interface.
vs Factory Method: Factory Method = one product via subclassing. Abstract Factory = whole family via composition. Abstract Factory often uses Factory Methods internally.