🦆 Analogy
You ask a toy shop for “a duck.” You don’t say how to build it or which exact model — you name what you want, and the shop hands you one that quacks. Swap the shop and “a duck” might come out as a rubber squeaker instead. You program against “duck,” not against a specific factory floor.
The problem
When code does d := &MallardDuck{}, it’s welded to that concrete type. Every place that constructs one must change if you add a RubberDuck, and tests can’t substitute a fake. A factory moves the new decision behind a function that returns an interface, so callers depend only on behavior.
Idiomatic Go — the constructor & the Simple Factory
The everyday Go factory is a function returning an interface. A Simple Factory extends that with a switch when the caller chooses a kind at runtime. Edit and Run:
package main
import "fmt"
// Product: callers depend on this, not on concrete types.
type Duck interface{ Quack() }
type MallardDuck struct{}
func (MallardDuck) Quack() { fmt.Println("Quack") }
type RubberDuck struct{}
func (RubberDuck) Quack() { fmt.Println("Squeak") }
type DecoyDuck struct{}
func (DecoyDuck) Quack() { fmt.Println("...silence...") }
// The factory: ask for a kind, get back a Duck.
func NewDuck(kind string) (Duck, error) {
switch kind {
case "mallard":
return MallardDuck{}, nil
case "rubber":
return RubberDuck{}, nil
case "decoy":
return DecoyDuck{}, nil
default:
return nil, fmt.Errorf("unknown duck: %q", kind)
}
}
func main() {
for _, kind := range []string{"mallard", "rubber", "decoy"} {
d, err := NewDuck(kind)
if err != nil {
fmt.Println("error:", err)
continue
}
fmt.Printf("%-8s -> ", kind)
d.Quack()
}
}
🐹 Simple Factory isn't quite the GoF pattern
The duck switch above is a Simple Factory — a handy idiom, but not formally one of the 23. The real Factory Method makes the creation step itself something each context overrides. Here it is with a pizza store.
Factory Method proper — the pizza store
The order workflow is fixed — prepare, bake, cut, box — but which pizza gets created is a step each store provides. In Go (no subclassing), the base store delegates creation to an embedded creator, and NYPizzaStore/ChicagoPizzaStore each supply their own CreatePizza:
classDiagram
class PizzaStore {
<<interface>>
+OrderPizza(kind) Pizza
}
class NYPizzaStore {
+CreatePizza(kind) Pizza
}
class ChicagoPizzaStore {
+CreatePizza(kind) Pizza
}
class Pizza { <<interface>> }
PizzaStore <|.. NYPizzaStore
PizzaStore <|.. ChicagoPizzaStore
NYPizzaStore ..> Pizza : NY-style
ChicagoPizzaStore ..> Pizza : Chicago-style// OrderPizza is the fixed workflow; CreatePizza is the factory method.
func (s *PizzaStoreBase) OrderPizza(kind PizzaType) (Pizza, error) {
pizza, err := s.store.CreatePizza(kind) // varies per store
if err != nil {
return nil, err
}
pizza.Prepare()
pizza.Bake()
pizza.Cut()
pizza.Box()
return pizza, nil
}
Each store implements CreatePizza to build its own regional pizzas — same order flow, different products.
The registry variation — Go’s favorite factory
Instead of a hard-coded switch, register constructors in a map keyed by name. New kinds plug themselves in from their own package’s init() — the factory never changes when you add one. This is exactly how image.RegisterFormat, sql.Register, and most codec libraries work:
package main
import (
"fmt"
"sort"
)
type Duck interface{ Quack() string }
// registry maps a name to a constructor, populated at init time.
var registry = map[string]func() Duck{}
func Register(name string, ctor func() Duck) { registry[name] = ctor }
func New(name string) (Duck, error) {
ctor, ok := registry[name]
if !ok {
return nil, fmt.Errorf("unknown duck: %q", name)
}
return ctor(), nil
}
type Mallard struct{}
func (Mallard) Quack() string { return "Quack" }
type Rubber struct{}
func (Rubber) Quack() string { return "Squeak" }
// In real code each of these would live in its own file/package.
func init() {
Register("mallard", func() Duck { return Mallard{} })
Register("rubber", func() Duck { return Rubber{} })
}
func main() {
names := make([]string, 0, len(registry))
for n := range registry {
names = append(names, n)
}
sort.Strings(names) // deterministic output
for _, n := range names {
d, _ := New(n)
fmt.Printf("%-8s -> %s\n", n, d.Quack())
}
}
The win is open/closed: adding a Decoy duck means writing a new file with a Register call — no edit to New or any switch.
How the creational family compares
All five creational patterns hide new, but they answer different questions:
| Pattern | In one line | Reach for it when |
|---|---|---|
| Simple Factory | one function/switch returns a product | a small, fixed set of known kinds |
| Factory Method | each context overrides the creation step | the build varies by context/subtype |
| Abstract Factory | a factory of related products (families) | you need matched sets (NY dough + NY sauce) |
| Builder | step-by-step assembly of one complex object | many optional fields / staged construction |
| Prototype | clone a configured instance | new objects resemble an existing one |
In the standard library
sql.Open(driver, dsn)— returns a*sql.DBfor a driver registered by name.image.Decode— dispatches to a decoder registered viaimage.RegisterFormat.net.Listen("tcp", addr)— returns the rightnet.Listenerfor the network.
Pitfalls
⚠️ A factory for one type is just indirection
If there’s exactly one implementation, NewThing() returning a concrete struct is fine — don’t wrap it in an interface and a switch “just in case.” Add the factory when a second implementation (or a test fake) actually shows up. And prefer returning the concrete type from constructors when you can — “accept interfaces, return structs.”
When to use it — and when not
✅ Reach for it when
- Callers should depend on an interface, not on concrete types — so you can swap implementations freely.
- You want a single place that decides which concrete type to build (and can validate the request).
- Construction needs to vary by context — a NY store builds NY pizzas, a Chicago store builds Chicago ones.
⛔ Think twice when
- There's only one implementation and no prospect of another — a plain struct literal is clearer.
- You're adding a factory purely speculatively; introduce it when variation actually appears.
Related patterns
Provide an interface for creating families of related objects without specifying their concrete types.
creationalBuilderConstruct a complex object step by step, separating how it's built from its final representation.
creationalPrototypeCreate new objects by cloning an existing, configured instance instead of building one from scratch.
behavioralStrategyDefine a family of interchangeable algorithms, encapsulate each one, and select which to use at runtime.
Check your understanding
Score: 0 / 51. What is the idiomatic 'factory' in everyday Go?
Go's everyday factory is just a constructor function returning an interface — callers get behavior without naming the concrete type.
2. How does a 'Simple Factory' differ from the GoF 'Factory Method'?
Simple Factory is one function/method with a switch. Factory Method makes creation an overridable step so different creators (NY vs Chicago store) build different products through the same workflow.
3. Which standard-library function is a factory?
sql.Open looks up a registered driver by name and returns a handle — you depend on the name, not the concrete driver type. image.Decode and net.Listen are similar.
4. What does the 'registry' factory variation buy you over a hard-coded switch?
A registry maps a name to a constructor func. New kinds call Register from their own package's init(), so the factory never changes when you add one — exactly how image.RegisterFormat and sql.Register work.
5. Factory Method vs Abstract Factory — what's the key difference?
Factory Method varies the creation of a single product per context. Abstract Factory groups several related creation methods so you get matched sets (NY dough + NY sauce, or Chicago dough + Chicago sauce) from one factory.
Comments
Sign in with GitHub to join the discussion.