{} The Go Reference

Creational pattern · Gang of Four · Beginner

Factory Method

Define an interface for creating an object, but let the implementation decide which concrete type to instantiate.

Also known as — Virtual Constructor

Creational Beginner ⏱ 5 min read Complete

🦆 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:

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

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

PatternIn one lineReach for it when
Simple Factoryone function/switch returns a producta small, fixed set of known kinds
Factory Methodeach context overrides the creation stepthe build varies by context/subtype
Abstract Factorya factory of related products (families)you need matched sets (NY dough + NY sauce)
Builderstep-by-step assembly of one complex objectmany optional fields / staged construction
Prototypeclone a configured instancenew objects resemble an existing one

In the standard library

  • sql.Open(driver, dsn) — returns a *sql.DB for a driver registered by name.
  • image.Decode — dispatches to a decoder registered via image.RegisterFormat.
  • net.Listen("tcp", addr) — returns the right net.Listener for 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.

Check your understanding

Score: 0 / 5

1. 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.