Design Patterns, Explained Simply · Creational Patterns: Creating Objects Flexibly

Abstract Factory

Create whole families of related objects that must match, without the code that uses them knowing which family it got.

The problem

A UI kit supports a light theme and a dark theme. Each screen builds buttons, checkboxes and text fields. Somewhere, a developer creates a DarkButton next to a LightCheckbox, and the settings page ships with a white checkbox on a black panel.

Every screen is full of if theme == "dark" checks, and adding a "high contrast" theme means finding every one of them.

Real-world analogy

A furniture showroom sells matching sets: a Victorian chair, sofa and table, or a modern chair, sofa and table. You pick a style catalogue, and everything you order from it matches. You never accidentally get a Victorian chair with a modern table.

Without the pattern

def settings_page(theme):
    button = DarkButton("Save") if theme == "dark" else LightButton("Save")
    checkbox = LightCheckbox("Notifications")   # oops: forgot the theme check here
    return [button, checkbox]

The pattern

  • Abstract factory (Theme): an interface with one creation method per product kind: button(), checkbox().
  • Concrete factories (LightTheme, DarkTheme): each creates products from one family.
  • Abstract products (Button, Checkbox) and concrete products (LightButton, DarkCheckbox, ...).
  • Client (settings_page): receives one factory and creates everything through it, so the family is always consistent.

The code

One theme factory creates matching widgetsTry it yourself
class LightButton:
    def __init__(self, label):
        self.label = label

    def render(self):
        return f"[ {self.label} ] (black text on white)"


class LightCheckbox:
    def __init__(self, label):
        self.label = label

    def render(self):
        return f"[x] {self.label} (grey box)"


class DarkButton:
    def __init__(self, label):
        self.label = label

    def render(self):
        return f"[ {self.label} ] (white text on charcoal)"


class DarkCheckbox:
    def __init__(self, label):
        self.label = label

    def render(self):
        return f"[x] {self.label} (outlined box)"


class LightTheme:  # concrete factory: one family
    def button(self, label):
        return LightButton(label)

    def checkbox(self, label):
        return LightCheckbox(label)


class DarkTheme:
    def button(self, label):
        return DarkButton(label)

    def checkbox(self, label):
        return DarkCheckbox(label)


def settings_page(theme):  # client: never names a concrete widget class
    widgets = [theme.checkbox("Email me updates"), theme.button("Save")]
    return [w.render() for w in widgets]


for theme in [LightTheme(), DarkTheme()]:
    print(f"{type(theme).__name__}:")
    for line in settings_page(theme):
        print("  " + line)

Output

LightTheme:
  [x] Email me updates (grey box)
  [ Save ] (black text on white)
DarkTheme:
  [x] Email me updates (outlined box)
  [ Save ] (white text on charcoal)

The theme is chosen once, at startup, and every widget on every page matches. A high-contrast theme is one new factory class plus its widgets; no page changes.

When to use it

  • You create several related objects that must be used together: UI widgets per theme or platform, database connection + command + transaction per vendor, cloud storage + queue + secrets per provider.
  • You want to switch the whole family in one place (config, environment, tests).

When not to use it

  • You only create one kind of object. That is a Factory Method or simple factory.
  • The families rarely change and there are only two. The extra interfaces may cost more than an occasional if.
  • Adding a new product kind (say, a slider) means changing the factory interface and every factory. If product kinds change often, the pattern hurts.

You've already used it

  • Java: DocumentBuilderFactory and JDBC drivers; a driver supplies matching Connection, Statement and ResultSet implementations.
  • Python: DB-API drivers (sqlite3, psycopg) supply matching connection and cursor types.
  • Cross-platform UI toolkits: one factory per operating system creates native-looking widgets.

Key points

  • Abstract Factory creates families of related objects that must match.
  • The client gets one factory and never names concrete product classes.
  • Switching families is a one-line change; adding a new product kind is expensive.
  • Factory Method creates one product via subclassing; Abstract Factory is an object with several creation methods.
  • Further reading: Refactoring Guru: Abstract Factory.

Practice questions

When would you choose Abstract Factory over Factory Method?

Answer

When you need to create several related objects that must match (a family), such as a button, a checkbox and a dialog for one theme. Factory Method creates a single product through an overridable method; Abstract Factory is an object with one creation method per product kind.

What is the main drawback of Abstract Factory?

Answer

Adding a new kind of product (say, a slider) means changing the abstract factory interface and every concrete factory. It is easy to add new families, hard to add new product kinds.

Give a real-world example of Abstract Factory.

Answer

Database drivers: a JDBC or DB-API driver supplies matching connection, statement/cursor and result types for one database vendor. UI toolkits that create native widgets per operating system are another example.

Open in the app to mark lessons complete, take notes and track your progress.