Chapter 03 · Article 12 of 55

Abstract Factory Pattern

Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Article outline14 sections on this page

Overview

Intent: Provide an interface for creating families of related or dependent objects without specifying their concrete classes.

The Abstract Factory pattern is a creational design pattern that encapsulates a group of individual factories sharing a common theme. The client code works with factories and products through abstract interfaces, making it independent of the actual concrete classes being instantiated.

Unlike a single factory that produces one type of object, an Abstract Factory produces an entire family of related products. The key guarantee is consistency within a family - if you use a Windows factory, every widget it produces will be a Windows-style widget. You never accidentally mix a macOS button with a Windows checkbox.

Also Known As: Kit

Pattern Type: Creational (Object)

Complexity: ** (Moderate)


Problem It Solves

Consider building a cross-platform desktop application. Your UI layer needs buttons, checkboxes, text fields, and scroll bars. Each platform (Windows, macOS, Linux) has its own look-and-feel for these widgets. Without Abstract Factory, you face several problems:

  1. Inconsistent object families - Scattered if/else or switch blocks throughout the codebase risk mixing platform-specific widgets. A single missed condition means a macOS checkbox appears in a Windows-themed window.

  2. Tight coupling to concrete classes - Client code that directly instantiates WindowsButton or MacCheckbox becomes impossible to port or extend without modifying every instantiation site.

  3. Violation of the Open/Closed Principle for families - Adding a new platform (e.g., Linux) requires hunting through every creation point rather than adding a single new factory class.

  4. Theme/skin systems - Applications supporting light/dark themes or brand-specific styling need a mechanism to swap entire visual families at runtime without rewriting rendering logic.

  5. Database abstraction layers - Connecting to MySQL vs. PostgreSQL requires consistent families of connections, commands, readers, and adapters that must not be mixed.

The Abstract Factory solves these by centralizing family creation behind a single interface, guaranteeing that all products from one factory are compatible with each other.


When to Use / When NOT to Use

When to UseWhen NOT to Use
System must be independent of how products are created and composedYou only need to create a single type of object (use Factory Method instead)
System must work with multiple families of related productsProduct families are unlikely to change or expand
You need to enforce consistency among related objectsThe added abstraction layers introduce unnecessary complexity
Product families are designed to be used together exclusivelyObjects in the "family" are actually independent and don't need consistency
You want to provide a library of products revealing only interfacesYou have only one concrete family and no foreseeable need for more
Runtime switching between product families is required (themes, platforms)Performance-critical paths where virtual dispatch overhead matters
You want to isolate concrete classes from client codeThe number of product types changes frequently (each addition modifies all factories)

Key Concepts & Theory

Factory of Factories

The Abstract Factory is sometimes described as a "factory of factories." More precisely, it is a super-factory that defines the interface for creating each product in a family. Each concrete factory implements this interface to produce platform-specific or theme-specific variants.

Product Families

A product family is a set of related objects designed to work together. For example:

  • Windows family: WindowsButton, WindowsCheckbox, WindowsTextField
  • Mac family: MacButton, MacCheckbox, MacTextField

The Abstract Factory guarantees that if you choose the Windows factory, you get all Windows products - never a mix.

Consistency Guarantee

The pattern's primary value proposition is intra-family consistency. Because a single factory object produces all related products, it is structurally impossible for client code to accidentally combine incompatible objects. This is enforced at compile time through the type system.

Platform Abstraction

Client code programs against abstract product interfaces (Button, Checkbox) and the abstract factory interface (GUIFactory). The concrete platform is selected once - typically at application startup via configuration or environment detection - and injected into the client. The client never knows which concrete family it is using.

Dependency Inversion

The pattern is a textbook application of the Dependency Inversion Principle: high-level modules (client/application logic) depend on abstractions (abstract factory + abstract products), not on low-level modules (concrete factories + concrete products).


ASCII Class Diagram

┌─────────────────────────────────┐
│      <<interface>>              │
│       AbstractFactory           │
├─────────────────────────────────┤
│ + createProductA(): AbstractProductA │
│ + createProductB(): AbstractProductB │
└──────────┬──────────┬───────────┘
           │          │
     ┌─────┘          └─────┐
     │                       │
     ▼                       ▼
┌──────────────┐     ┌──────────────┐
│ConcreteFactory1│     │ConcreteFactory2│
├──────────────┤     ├──────────────┤
│+createProductA()│   │+createProductA()│
│+createProductB()│   │+createProductB()│
└──────┬───────┘     └──────┬───────┘
       │ creates             │ creates
       ▼                     ▼
┌──────────────┐     ┌──────────────┐
│ConcreteProductA1│   │ConcreteProductA2│
└──────────────┘     └──────────────┘
┌──────────────┐     ┌──────────────┐
│ConcreteProductB1│   │ConcreteProductB2│
└──────────────┘     └──────────────┘

       ▲                     ▲
       │ implements          │ implements
       │                     │
┌──────────────────┐  ┌──────────────────┐
│ <<interface>>    │  │ <<interface>>    │
│ AbstractProductA │  │ AbstractProductB │
├──────────────────┤  ├──────────────────┤
│ + operationA()   │  │ + operationB()   │
└──────────────────┘  └──────────────────┘

Client ──────► AbstractFactory
Client ──────► AbstractProductA
Client ──────► AbstractProductB
(Client depends ONLY on abstractions)

Pseudocode Implementation

Abstract Products

interface Button {
    method render()
    method onClick(handler)
}

interface Checkbox {
    method render()
    method toggle()
    method isChecked(): boolean
}

interface TextField {
    method render()
    method getValue(): string
    method setValue(text: string)
}

Abstract Factory

interface GUIFactory {
    method createButton(label: string): Button
    method createCheckbox(label: string): Checkbox
    method createTextField(placeholder: string): TextField
}

Concrete Products - Windows Family

class WindowsButton implements Button {
    method render() {
        // Draw button with Windows flat style, Segoe UI font
        drawRect(style: "win-flat", font: "Segoe UI")
    }
    method onClick(handler) {
        registerWin32ClickEvent(handler)
    }
}

class WindowsCheckbox implements Checkbox {
    field checked = false
    method render() {
        // Draw square checkbox with checkmark, Windows style
        drawWinCheckbox(checked: this.checked)
    }
    method toggle() { this.checked = !this.checked }
    method isChecked() { return this.checked }
}

class WindowsTextField implements TextField {
    field text = ""
    method render() {
        // Draw single-line edit control, Windows style
        drawWinEditControl(placeholder: this.placeholder)
    }
    method getValue() { return this.text }
    method setValue(t) { this.text = t }
}

Concrete Products - Mac Family

class MacButton implements Button {
    method render() {
        // Draw button with macOS rounded style, SF Pro font
        drawRoundedRect(style: "aqua", font: "SF Pro")
    }
    method onClick(handler) {
        registerCocoaAction(handler)
    }
}

class MacCheckbox implements Checkbox {
    field checked = false
    method render() {
        // Draw rounded checkbox with blue tint, macOS style
        drawMacCheckbox(checked: this.checked)
    }
    method toggle() { this.checked = !this.checked }
    method isChecked() { return this.checked }
}

class MacTextField implements TextField {
    field text = ""
    method render() {
        // Draw NSTextField-style input, macOS style
        drawMacTextField(placeholder: this.placeholder)
    }
    method getValue() { return this.text }
    method setValue(t) { this.text = t }
}

Concrete Factories

class WindowsFactory implements GUIFactory {
    method createButton(label) {
        return new WindowsButton(label)
    }
    method createCheckbox(label) {
        return new WindowsCheckbox(label)
    }
    method createTextField(placeholder) {
        return new WindowsTextField(placeholder)
    }
}

class MacFactory implements GUIFactory {
    method createButton(label) {
        return new MacButton(label)
    }
    method createCheckbox(label) {
        return new MacCheckbox(label)
    }
    method createTextField(placeholder) {
        return new MacTextField(placeholder)
    }
}

Client Code

class SettingsDialog {
    field factory: GUIFactory
    field saveButton: Button
    field darkModeCheckbox: Checkbox
    field usernameField: TextField

    constructor(factory: GUIFactory) {
        this.factory = factory
    }

    method initialize() {
        this.saveButton = factory.createButton("Save")
        this.darkModeCheckbox = factory.createCheckbox("Dark Mode")
        this.usernameField = factory.createTextField("Enter username...")
    }

    method render() {
        this.usernameField.render()
        this.darkModeCheckbox.render()
        this.saveButton.render()
        this.saveButton.onClick(() => this.save())
    }

    method save() {
        username = this.usernameField.getValue()
        darkMode = this.darkModeCheckbox.isChecked()
        // persist settings...
    }
}

// --- Application Bootstrap ---
method main() {
    config = readConfig()

    factory: GUIFactory
    if config.os == "windows" {
        factory = new WindowsFactory()
    } else if config.os == "mac" {
        factory = new MacFactory()
    }

    dialog = new SettingsDialog(factory)
    dialog.initialize()
    dialog.render()
}

The client (SettingsDialog) has zero knowledge of Windows or Mac classes. Swapping platforms requires changing only the factory instantiation at the bootstrap level.


Abstract Factory vs Factory Method

AspectFactory MethodAbstract Factory
ScopeCreates a single productCreates families of related products
Abstraction LevelOne method in a class defers instantiation to subclassesAn entire object dedicated to creating a product family
Number of ProductsOne product per factory methodMultiple products per factory
ImplementationUses inheritance (subclass overrides creation method)Uses composition (client holds a factory object)
ComplexityLow - single method overrideModerate - interface with multiple creation methods
Consistency GuaranteeNone across productsGuarantees all products belong to same family
Adding New ProductsAdd a new factory method (easy)Must modify the abstract factory interface (hard)
Adding New FamiliesCreate a new subclassCreate a new concrete factory (easy)
Client CouplingClient may still know the creator subclassClient knows only the abstract factory interface
Typical Use CaseFramework hook for object creationPlatform/theme/brand abstraction
GoF ClassificationClass-level creationalObject-level creational
RelationshipAbstract Factory often uses Factory Methods internallyAbstract Factory is a higher-level pattern

Rule of thumb: If you need one product, start with Factory Method. If you need a consistent set of products, graduate to Abstract Factory.


Real-World Examples

1. Cross-Platform UI Toolkits

Qt Framework uses an abstract factory approach internally. QPlatformTheme and QPlatformIntegration serve as abstract factories that produce platform-specific dialogs, fonts, icons, and system tray implementations. Each platform plugin (xcb, cocoa, windows) provides concrete factories.

Java AWT/Swing - java.awt.Toolkit is an abstract factory. Toolkit.getDefaultToolkit() returns a platform-specific toolkit (WToolkit on Windows, XToolkit on Linux) that creates native peers for every AWT component.

2. Database Driver Families

ADO.NET's DbProviderFactory is a classic Abstract Factory:

  • SqlClientFactory → creates SqlConnection, SqlCommand, SqlDataReader
  • OracleClientFactory → creates OracleConnection, OracleCommand, OracleDataReader

Each factory produces a consistent family of database objects. Client code works with DbConnection, DbCommand, etc., never knowing the concrete database.

3. Document Format Families

A document export system might define:

  • PDFFactory → creates PDFHeading, PDFParagraph, PDFTable, PDFImage
  • HTMLFactory → creates HTMLHeading, HTMLParagraph, HTMLTable, HTMLImage
  • MarkdownFactory → creates MDHeading, MDParagraph, MDTable, MDImage

The document builder works with abstract DocumentElement interfaces, and the chosen factory ensures all elements render in the same format.

4. Cloud Provider Abstraction

Multi-cloud libraries abstract away provider differences:

  • AWSFactory → creates S3Storage, SQSQueue, LambdaFunction
  • GCPFactory → creates GCSStorage, PubSubQueue, CloudFunction

Advantages & Disadvantages

AdvantagesDisadvantages
Isolation of concrete classes - Client never references concrete product namesDifficulty adding new product types - Adding a new product requires changing the abstract factory interface and ALL concrete factories
Consistency guarantee - Products from one factory are always compatibleIncreased complexity - Many new interfaces and classes for each family
Easy family swapping - Change one factory object to switch entire product familyOverkill for simple cases - If you have one family or one product, the pattern adds unnecessary abstraction
Single Responsibility - Product creation code is centralized in one placeRigid interface - The abstract factory fixes the set of products at design time
Open/Closed for new families - Adding a new platform means adding one factory classExplosion of classes - N families × M products = N×M concrete product classes + N factory classes
Promotes programming to interfaces - Enforces dependency inversion naturallyCan obscure code flow - Indirection makes it harder to trace which concrete class is instantiated
Testability - Inject mock factories for unit testingPerformance overhead - Virtual dispatch and object allocation for every product

Constraints & Edge Cases

Adding New Product Types Violates OCP

The most significant constraint: if you add a new product (e.g., Slider) to the abstract factory interface, every concrete factory must be updated. This violates the Open/Closed Principle along the product-type axis. Mitigation strategies:

  • Use a parameterized factory method with a product-type enum (sacrifices type safety)
  • Use the Prototype pattern - factories store prototypical instances and clone them
  • Accept the trade-off: Abstract Factory is open for new families but closed for new product types

Complexity Overhead

For systems with only 1-2 product types, Abstract Factory introduces unnecessary indirection. A simple Factory Method or even direct construction with dependency injection may suffice. Apply the pattern when you have ≥3 related products AND ≥2 families.

When Factory Method Suffices

If products don't need to be consistent with each other - e.g., you create buttons independently of checkboxes - Factory Method per product type is simpler and more flexible.

Singleton Factories

Concrete factories are often implemented as Singletons since they carry no mutable state. This is acceptable but be cautious in multi-threaded environments if factories cache products.

Lazy vs. Eager Creation

Some implementations create all products upfront (eager), while others create on demand (lazy). Lazy creation is preferred for expensive objects but complicates error handling - a creation failure mid-workflow leaves the system in a partially initialized state.

Serialization Boundaries

When products cross serialization boundaries (network, disk), the receiving side needs the correct factory to reconstruct objects. Pass a factory identifier alongside serialized data.


Interview Follow-ups

Q1: How does Abstract Factory differ from Builder pattern?

A: Abstract Factory focuses on what family of objects to create (the product selection), while Builder focuses on how to construct a complex object step by step. Abstract Factory returns products immediately; Builder accumulates construction steps and returns the final product at the end. They can be combined: a Builder might use an Abstract Factory to obtain the parts it assembles.

Q2: How would you add a new product type without modifying existing factories?

A: Three approaches: (1) Use a default method in the abstract factory interface (languages supporting this) that throws UnsupportedOperationException - existing factories compile but fail at runtime for the new product. (2) Create a separate, parallel factory interface for the new product and compose it with the existing factory. (3) Use the Extension Interface pattern - define optional product creation in a separate interface that only some factories implement, and have clients check with instanceof before calling.

Q3: Can Abstract Factory work with Dependency Injection frameworks?

A: Yes, and it's a natural fit. The DI container acts as the bootstrap that selects and injects the correct concrete factory based on configuration. Spring's @Profile annotation, for example, can activate a WindowsFactory bean in Windows environments and a MacFactory bean on macOS. The factory itself is injected into client classes, preserving the pattern's decoupling benefits while eliminating manual if/else selection logic.

Q4 (Hint Only): How would you implement Abstract Factory in a language without interfaces (e.g., C)?

Hint: Think about structs containing function pointers. Each "concrete factory" is a struct instance with its function pointer fields assigned to platform-specific creation functions. The client receives a pointer to this struct and calls through the function pointers.

Q5 (Hint Only): How does Abstract Factory interact with the Prototype pattern to solve the "new product type" problem?

Hint: Instead of a fixed set of creation methods, the factory stores a registry (map) of product-type keys to prototypical instances. Creating a product means looking up the prototype and cloning it. Adding a new product type means registering a new prototype - no interface change required.


Counter Questions to Ask Interviewer

  1. "How many product families do we anticipate?" - If only one family exists today with no foreseeable second, Abstract Factory is premature. Understanding growth expectations determines whether the pattern's overhead is justified.

  2. "Do all products in a family need to be consistent, or can they be mixed?" - If mixing is acceptable (e.g., a Windows button with a custom-styled checkbox), the consistency guarantee is unnecessary and Factory Method per product is simpler.

  3. "How frequently do new product types get added vs. new families?" - Abstract Factory excels when new families are added often but product types are stable. If product types change frequently, the pattern becomes a maintenance burden.

  4. "Is runtime family switching required, or is the family fixed at startup?" - Runtime switching (e.g., live theme changes) requires the factory to be swappable, which influences whether products hold back-references to their factory.

  5. "What's the deployment model - monolith or microservices?" - In microservices, each service might only need one family, making the abstraction unnecessary within a single service. The pattern may still apply at the API gateway or SDK level.


References & Whitepapers

  1. Gamma, E., Helm, R., Johnson, R., Vlissides, J. - Design Patterns: Elements of Reusable Object-Oriented Software (1994), Chapter 3: Creational Patterns - Abstract Factory. The foundational reference defining the pattern, its structure, participants, and consequences.

  2. Qt Framework Architecture - Qt's platform abstraction layer (QPlatformIntegration, QPlatformTheme) demonstrates industrial-scale Abstract Factory usage. Each platform plugin implements these interfaces to produce native widgets, dialogs, and system services. See: Qt Platform Abstraction

  3. Freeman, E., Robson, E. - Head First Design Patterns (2004), Chapter 4. Provides an accessible pizza-store analogy for Abstract Factory with Java examples.

  4. Martin, R.C. - Agile Software Development: Principles, Patterns, and Practices (2002). Discusses Abstract Factory in the context of the Dependency Inversion Principle and package design.

  5. Microsoft ADO.NET Documentation - DbProviderFactory class hierarchy. A production implementation of Abstract Factory for database-agnostic data access. See: DbProviderFactory Class

  6. Bloch, J. - Effective Java (3rd Edition, 2018), Item 64: "Refer to objects by their interfaces." Reinforces the principle that Abstract Factory leverages.


  • Factory Method - Single-product creation via inheritance; often used internally by Abstract Factory
  • Builder Pattern - Step-by-step construction of complex objects; complementary to Abstract Factory
  • Prototype Pattern - Clone-based creation; alternative approach to avoiding the "new product type" problem
  • Singleton Pattern - Concrete factories are often implemented as Singletons
  • Dependency Injection - Modern alternative/complement that achieves similar decoupling
  • Bridge Pattern - Separates abstraction from implementation; often used alongside Abstract Factory
  • Strategy Pattern - Abstract Factory can be seen as a Strategy for object creation