Abstract Factory Pattern

Families of related objects without specifying concrete classes. When one factory isn't enough.

Pattern Intermediate
8 min read
pattern creational abstract-factory families

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 WidgetFactory with MacFactory, WindowsFactory, LinuxFactory concrete implementations.
  • Cloud-portable infrastructure — a CloudFactory whose AwsFactory produces S3Storage + SqsQueue + LambdaFunction, while GcpFactory produces GcsStorage + PubSubQueue + CloudFunction.
  • Multi-database persistencePersistenceFactory with MysqlFactory (producing MysqlConnection + MysqlQueryBuilder + MysqlDialect) vs PostgresFactory.
  • Theme systemsThemeFactory whose DarkFactory and LightFactory produce matching Colors, Typography, and Icons.

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. CheckoutForm cannot accidentally pair a MacButton with a WinCheckbox; it never names either. The factory enforces the family at compile time.
  • Adding a product family is easy. A new LinuxFactory plus LinuxButton / LinuxCheckbox — and clients are untouched.
  • Adding a product type is hard. Adding a Menu means editing GuiFactory and 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 WinFactory instance 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#

VariantMechanismWhen it fits
Classical (interface)AbstractFactory is an interface; each family is an implementation.The default; what most descriptions mean.
Abstract class with defaultsAbstractFactory 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-basedEach 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 matching DocumentBuilder + 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 — produces Statement, PreparedStatement, and CallableStatement that all match the database vendor. The vendor’s Connection implementation is the concrete factory.
  • AWT toolkitToolkit.getDefaultToolkit() returns a platform-specific factory producing platform-specific peers for widgets.
  • java.nio.file.FileSystem.provider() — different FileSystemProvider implementations produce matching Path + 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 LinuxFactory is purely additive — no edits to existing code.
  • Testability. A TestFactory returning 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 CheckoutForm does 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.
  • 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 MacFactory per 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.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.