Abstract Factory Pattern
Families of related objects without specifying concrete classes. When one factory isn't enough.
What it is#
The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. Where Factory Method picks one product, Abstract Factory picks a suite of products that must work together.
The roles: an AbstractFactory interface declares creation methods for each product in the family (createButton(), createCheckbox(), createMenu()); ConcreteFactory classes implement those methods to return matching variants (MacOSFactory returns a MacOSButton, a MacOSCheckbox, a MacOSMenu); AbstractProduct interfaces describe each kind of product; ConcreteProduct classes are the implementations. Client code holds an AbstractFactory reference and never names a concrete product — it asks the factory for each piece, and the factory guarantees the pieces match.
The pattern is “one factory isn’t enough.” When a system has multiple product axes that must vary together — every Mac widget must be a Mac widget, every Windows widget must be a Windows widget, you cannot mix — Abstract Factory is the structure that enforces the constraint at compile time.
Class structure#
┌────────────────────────┐ ┌─────────────────┐ │ AbstractFactory │ creates│ AbstractProductA│ ├────────────────────────┤───────►├─────────────────┤ │ + createProductA() : A │ │ + opA() │ │ + createProductB() : B │ └────────▲────────┘ └──────────▲─────────────┘ │ │ ┌───────────┴────────┐ │ │ │ ┌──────────┴────────┐ ┌──────┴──────┐ ┌────────┴────┐ │ ConcreteFactory1 │ │ ProductA1 │ │ ProductA2 │ ├───────────────────┤ └─────────────┘ └─────────────┘ │ createProductA() │ │ createProductB() │ ┌─────────────────┐ └───────────────────┘ │ AbstractProductB│ ┌───────────────────┐ ├─────────────────┤ │ ConcreteFactory2 │ │ + opB() │ ├───────────────────┤ └────────▲────────┘ │ createProductA() │ │ │ createProductB() │ ┌─────────┴──────────┐ └───────────────────┘ │ │ ┌─────┴───────┐ ┌───────┴─────┐ │ ProductB1 │ │ ProductB2 │ └─────────────┘ └─────────────┘ConcreteFactory1 produces ProductA1 + ProductB1 (one family); ConcreteFactory2 produces ProductA2 + ProductB2 (the other). Mixing across families is impossible by construction.
When to use it#
Reach for Abstract Factory when all of these hold:
- The system needs to be independent of how its products are created and composed.
- Products come in families that must be used together — mixing a Mac button with a Windows menu is wrong.
- You want to enforce the “must match” constraint at the type system level, not at runtime.
- New families will be added over time; new products within a family are rarer.
Concrete shapes this takes:
- Cross-platform UI toolkits — the textbook example. A
WidgetFactorywithMacFactory,WindowsFactory,LinuxFactoryconcrete implementations. - Cloud-portable infrastructure — a
CloudFactorywhoseAwsFactoryproducesS3Storage+SqsQueue+LambdaFunction, whileGcpFactoryproducesGcsStorage+PubSubQueue+CloudFunction. - Multi-database persistence —
PersistenceFactorywithMysqlFactory(producingMysqlConnection+MysqlQueryBuilder+MysqlDialect) vsPostgresFactory. - Theme systems —
ThemeFactorywhoseDarkFactoryandLightFactoryproduce matchingColors,Typography, andIcons.
When not to use it:
- When products are independent and do not need to match — use Factory Method Pattern per product.
- When the family axis is unlikely to grow — the indirection is overhead.
- When configuration, not type, is what varies — use Builder Pattern.
How it works#
A cross-platform UI toolkit example. Two product families — MacOS-styled widgets and Windows-styled widgets — each containing a Button and a Checkbox. Client code asks for a factory and uses it; client code never names a concrete widget class.
// Abstract products.public interface Button { void render(); }public interface Checkbox { void render(); }
// MacOS family.public final class MacButton implements Button { public void render() { System.out.println("[ macOS button ]"); } }public final class MacCheckbox implements Checkbox { public void render() { System.out.println("[ ⌘ ] macOS check"); } }
// Windows family.public final class WinButton implements Button { public void render() { System.out.println("|--- Win button ---|"); } }public final class WinCheckbox implements Checkbox { public void render() { System.out.println("[ X ] Win check"); } }
// Abstract factory.public interface GuiFactory { Button createButton(); Checkbox createCheckbox();}
// Concrete factories — each produces a matching family.public final class MacFactory implements GuiFactory { @Override public Button createButton() { return new MacButton(); } @Override public Checkbox createCheckbox() { return new MacCheckbox(); }}
public final class WinFactory implements GuiFactory { @Override public Button createButton() { return new WinButton(); } @Override public Checkbox createCheckbox() { return new WinCheckbox(); }}
// Client — depends only on the abstract factory and abstract products.public final class CheckoutForm { private final Button submit; private final Checkbox subscribe;
public CheckoutForm(GuiFactory factory) { this.submit = factory.createButton(); this.subscribe = factory.createCheckbox(); }
public void render() { subscribe.render(); submit.render(); }}
// Wiring (the only place that knows about concrete factories):// GuiFactory factory = isMac() ? new MacFactory() : new WinFactory();// new CheckoutForm(factory).render();Five things worth noticing:
- The constraint is structural.
CheckoutFormcannot accidentally pair aMacButtonwith aWinCheckbox; it never names either. The factory enforces the family at compile time. - Adding a product family is easy. A new
LinuxFactoryplusLinuxButton/LinuxCheckbox— and clients are untouched. - Adding a product type is hard. Adding a
Menumeans editingGuiFactoryand every concrete factory. This is the pattern’s central trade-off: it optimises for the “more families” axis at the cost of the “more products per family” axis. - The factory itself is often a singleton. One
WinFactoryinstance per application is fine; the products it makes are not singletons. - It composes with Factory Method. Each
createX()is internally a Factory Method; Abstract Factory is the structure that bundles several of them with a “must match” invariant.
Variants#
| Variant | Mechanism | When it fits |
|---|---|---|
| Classical (interface) | AbstractFactory is an interface; each family is an implementation. | The default; what most descriptions mean. |
| Abstract class with defaults | AbstractFactory is an abstract class providing common behaviour plus abstract creation hooks. | When concrete factories share construction logic (e.g. caching produced instances). |
| Functional (Java 8+) | Pass a Map<ProductType, Supplier<Product>> per family instead of subclassing. | Reduces boilerplate when the product axis is dynamic; loses some compile-time safety. |
| Prototype-based | Each factory holds a prototype of each product and clones on create(). | Composes with Prototype Pattern; helpful when products are expensive to construct from scratch. |
The functional variant is increasingly common — a Map<Os, Supplier<Button>> plus a Map<Os, Supplier<Checkbox>> does the same job with less code, at the cost of letting a misconfigured map mix families. Pick based on whether you trust the configuration code.
Example systems#
Real-world Abstract Factories:
javax.xml.parsers.DocumentBuilderFactory— produces matchingDocumentBuilder+ downstream parsers. Different implementations (Xerces, Crimson, etc.) are different families.javax.xml.transform.TransformerFactory— the same pattern for XSLT transformers and their templates.- JDBC
Connection— producesStatement,PreparedStatement, andCallableStatementthat all match the database vendor. The vendor’sConnectionimplementation is the concrete factory. - AWT toolkit —
Toolkit.getDefaultToolkit()returns a platform-specific factory producing platform-specific peers for widgets. java.nio.file.FileSystem.provider()— differentFileSystemProviderimplementations produce matchingPath+FileStore+ watchers.
Trade-offs#
What you gain:
- Family consistency by construction. Impossible to mix incompatible products; the type system rejects it.
- Isolation of concrete classes. Concrete factory names appear in exactly one place — the wiring code. Everywhere else depends on abstractions.
- Open for new families. Adding
LinuxFactoryis purely additive — no edits to existing code. - Testability. A
TestFactoryreturning stub products lets you test client code with no real widgets, no real database, no real cloud.
What you pay:
- Closed for new products. Adding a new product type (
Menu) requires editing the abstract factory and every concrete factory. Pick this pattern only when the “family” axis is the one that grows. - Two parallel hierarchies. Products × families. With three product types and three families, you have nine concrete classes plus three factories — the file count grows multiplicatively.
- The pattern hides the wiring. Reading a
CheckoutFormdoes not tell you which family it will use; you have to trace back to where the factory was constructed. - The factory itself can rot into a god object. A factory with thirty creation methods is a smell — split by sub-family.
Factory Method
- Creates one product per subclass.
- One creation method on the creator.
- Adding a new product is cheap.
- The hierarchy is product-shaped.
Abstract Factory
- Creates a family of products per factory.
- Multiple creation methods on the factory.
- Adding a new family is cheap; adding a new product is invasive.
- The hierarchy is family-shaped.
Related patterns#
- Factory Method Pattern — Abstract Factory is implemented using Factory Methods. The relationship is “uses,” not “extends.”
- Builder Pattern — Abstract Factory picks the family; Builder configures a single complex object. They compose when products themselves are non-trivial.
- Prototype Pattern — an alternative when the family is dynamic. Register prototype instances per family and clone instead of constructing.
- Singleton Pattern — concrete factories are often singletons. One
MacFactoryper process; many widgets per factory. - Open Closed Principle (OCP) — the pattern is open for new families and closed for product additions. Pick it when that asymmetry matches your reality.