Creational pattern · Gang of Four · Intermediate

Abstract Factory

Provide an interface for creating families of related objects without specifying their concrete types.

Also known as — Kit

Creational Intermediate Complete

🪑 Analogy

A furniture company sells matching sets. Pick the Victorian line and the chair, sofa, and table all share the same ornate style; pick Modern and they’re all sleek. You choose one factory, and everything it produces fits together. You never end up with a Victorian chair next to a Modern table.

The problem

Factory Method makes one product. But sometimes you need a set of products that must be consistent with each other — a pizza’s dough, sauce, and cheese should all be NY-style or all Chicago-style, never a mix. Abstract Factory bundles those creation methods so picking a factory picks a whole coherent family.

Structure

classDiagram
  class IngredientFactory {
    <<interface>>
    +Dough() Dough
    +Cheese() Cheese
  }
  class NYFactory {
    +Dough() Dough
    +Cheese() Cheese
  }
  class ChicagoFactory {
    +Dough() Dough
    +Cheese() Cheese
  }
  class Dough { <<interface>> }
  class Cheese { <<interface>> }
  IngredientFactory <|.. NYFactory
  IngredientFactory <|.. ChicagoFactory
  NYFactory ..> Dough : thin crust
  NYFactory ..> Cheese : reggiano

Idiomatic Go

The client works only with the IngredientFactory interface, so swapping NYFactory for ChicagoFactory swaps the entire ingredient set — and they always match. Edit and Run:

package main

import "fmt"

// Abstract products.
type Dough interface{ String() string }
type Cheese interface{ String() string }

// Abstract factory: makes a whole family of ingredients.
type IngredientFactory interface {
	Dough() Dough
	Cheese() Cheese
}

// --- NY family ---
type thinCrust struct{}
func (thinCrust) String() string { return "thin-crust dough" }
type reggiano struct{}
func (reggiano) String() string { return "reggiano cheese" }

type NYFactory struct{}
func (NYFactory) Dough() Dough   { return thinCrust{} }
func (NYFactory) Cheese() Cheese { return reggiano{} }

// --- Chicago family ---
type deepDish struct{}
func (deepDish) String() string { return "deep-dish dough" }
type mozzarella struct{}
func (mozzarella) String() string { return "mozzarella cheese" }

type ChicagoFactory struct{}
func (ChicagoFactory) Dough() Dough   { return deepDish{} }
func (ChicagoFactory) Cheese() Cheese { return mozzarella{} }

// Client depends only on the abstract factory — ingredients always match.
func makePizza(f IngredientFactory) {
	fmt.Printf("pizza with %s and %s\n", f.Dough(), f.Cheese())
}

func main() {
	makePizza(NYFactory{})
	makePizza(ChicagoFactory{})
}

A fuller version goes the whole distance: a PizzaIngredientFactory with six creation methods (dough, sauce, cheese, veggies, pepperoni, clam), and NYPizzaIngredientFactory returning ThinCrustDough, MarinaraSauce, ReggianoCheese, and so on. A pizza’s Prepare() simply asks its factory for each ingredient.

🐹 Go often prefers something lighter

Abstract Factory is rarer in idiomatic Go than in Java. When you only need a couple of related constructors, passing them in directly — or a small config struct of dependencies, or functional options — is usually clearer than a formal factory interface. Reach for the full pattern when the family abstraction genuinely earns its keep (pluggable backends, theme kits, platform-specific widget sets).

Factory Method vs Abstract Factory

Factory Method

  • Creates one product
  • Defers which concrete type
  • One creation method

Abstract Factory

  • Creates a family of products
  • Guarantees they're consistent
  • Several creation methods, often built from factory methods

In the standard library & ecosystem

Pure Abstract Factory is uncommon in Go’s stdlib (the language nudges you toward simpler construction), but the shape appears wherever a chosen provider yields a matched set — e.g. a database driver producing its own Conn, Stmt, and Tx types, or a cloud SDK whose “client” hands back service-specific request/response objects.

Pitfalls

⚠️ Adding a new product is the hard part

Abstract Factory shines when you add new families (just write one more concrete factory). It’s painful when you add a new product type — every existing factory must grow another method. If your products change more than your families do, reconsider the abstraction.

When to use it — and when not

✅ Reach for it when

  • You must create groups of objects that are meant to be used together and must stay consistent (all NY-style, or all Chicago-style).
  • You want to swap an entire family at once by choosing a different factory.
  • Clients should depend only on the abstract products and factory, never the concrete families.

⛔ Think twice when

  • There's only one family, or families almost never change — the extra layer is overhead.
  • In Go, passing the few constructor funcs you need (or a config struct) is often simpler than a full abstract factory.

Check your understanding

Score: 0 / 3

1. What does an Abstract Factory create?

It produces a whole set — dough, sauce, cheese — guaranteed to belong to the same family, so a NY factory never mixes in a Chicago ingredient.

2. How does Abstract Factory relate to Factory Method?

Factory Method creates one product and defers its type. Abstract Factory groups several such creation methods so a whole family is produced consistently.

3. What guarantees the products match?

Because NYIngredientFactory's methods each return NY ingredients, anything built through it is internally consistent — the client can't accidentally mix families.