What It Is
The Abstract Factory pattern creates whole families of related objects that belong together. Where a factory method makes one product, an abstract factory makes a matching set — and guarantees the pieces are never mixed.
The classic case is theming: a light theme must produce light buttons and light labels, never a mix.
Example: A Theme Factory
renderScreen takes any UIFactory and builds a consistent screen from it. Swapping the whole look is one argument — and mixing themes is impossible by construction.
One factory, one consistent family
The light theme produces only light parts, the dark theme only dark ones. renderScreen never changes.
Output: Go program and captured output
package main
import "fmt"
type Button interface{ Render() string }
type Label interface{ Render() string }
// The abstract factory makes a whole matching family.
type UIFactory interface {
NewButton() Button
NewLabel() Label
}
type LightButton struct{}
func (LightButton) Render() string { return "[ OK ] light button" }
type LightLabel struct{}
func (LightLabel) Render() string { return "hello light label" }
type DarkButton struct{}
func (DarkButton) Render() string { return "[ OK ] dark button" }
type DarkLabel struct{}
func (DarkLabel) Render() string { return "hello dark label" }
type LightTheme struct{}
func (LightTheme) NewButton() Button { return LightButton{} }
func (LightTheme) NewLabel() Label { return LightLabel{} }
type DarkTheme struct{}
func (DarkTheme) NewButton() Button { return DarkButton{} }
func (DarkTheme) NewLabel() Label { return DarkLabel{} }
func renderScreen(f UIFactory) {
fmt.Println(f.NewLabel().Render())
fmt.Println(f.NewButton().Render())
}
func main() {
renderScreen(LightTheme{})
renderScreen(DarkTheme{})
}
When to Use It
Use it when several objects must stay consistent with each other — themes, platform widget sets, matching import/export format pairs.
Skip it when the products do not need to match. Independent objects deserve independent constructors, not a family factory.
Pros and Cons
Pros: family consistency is guaranteed; whole families swap with one value; calling code depends only on interfaces.
Cons: a lot of interfaces and types for a small job; adding a new product to the family means touching every factory.