{} The Go Reference

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 ⏱ 4 min read 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:

abstract_factory.go — editable & runnable
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

Design principle: Dependency Inversion

Abstract Factory is the textbook expression of the Dependency Inversion Principledepend on abstractions, not concretions. The client (makePizza) names only IngredientFactory, Dough, and Cheese; it never mentions NYFactory or deepDish. So the high-level policy and the low-level concrete families both point at the same abstractions, and neither depends on the other. That inversion is precisely what lets you swap a whole family by passing a different factory value.

In practice an Abstract Factory is often built from Factory Methods (each creation method is one) and frequently hands back a single shared instance via a Singleton — the three creational patterns compose naturally.

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 / 5

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.

4. Adding a new *family* vs a new *product type* — which is easy and which is hard?

The interface fixes the set of products. Adding a family just implements that interface once more — open for extension. Adding a product type changes the interface, forcing an edit to every existing factory — a closed-for-modification violation. If products churn more than families, reconsider.

5. Which design principle does Abstract Factory most embody?

Both the high-level client and the low-level concrete families depend on abstractions (the factory interface + product interfaces), not on each other's concrete types. That inverted dependency is what lets you swap an entire family by passing a different factory.

Comments

Sign in with GitHub to join the discussion.