Mastering Go Design Patterns: Singleton, Factory, Strategy, and Observer Explained
This article explores four classic design patterns—Singleton, Factory, Strategy, and Observer—implemented in Go, demonstrating how to achieve their core principles using Go’s structs, interfaces, and concurrency features, and visualizing each pattern with UML diagrams for clearer understanding.
Design patterns provide proven solutions to recurring software design problems. In Go, a language that emphasizes simplicity and efficiency, these patterns can be expressed using structs, interfaces, and built‑in concurrency primitives. The following sections present four common patterns with explanations, code examples, and UML illustrations.
1. Singleton Pattern
The Singleton ensures that a type has only one instance and provides a global access point. In Go, this is typically achieved with a private struct, a package‑level variable, and sync.Once to guarantee thread‑safe lazy initialization.
package singleton
import "sync"
type singleton struct{}
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}The accompanying UML diagram shows a single class with a private constructor and a static accessor.
2. Factory Pattern
The Factory pattern provides an interface for creating objects while allowing subclasses to decide which concrete class to instantiate. Go lacks classical inheritance, so the pattern is realized with interfaces and concrete struct types.
package factory
type Product interface {
Use() string
}
type Factory struct{}
func (f *Factory) CreateProduct(t string) Product {
if t == "A" {
return &ProductA{}
} else if t == "B" {
return &ProductB{}
}
return nil
}
type ProductA struct{}
func (p *ProductA) Use() string { return "ProductA" }
type ProductB struct{}
func (p *ProductB) Use() string { return "ProductB" }The UML diagram depicts the Product interface, concrete product classes, and the Factory that creates them.
3. Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. In Go, the pattern is expressed with a strategy interface and concrete strategy structs, while a context struct holds a reference to the current strategy.
package strategy
type Strategy interface {
Execute() string
}
type ConcreteStrategyA struct{}
func (s *ConcreteStrategyA) Execute() string { return "Strategy A" }
type ConcreteStrategyB struct{}
func (s *ConcreteStrategyB) Execute() string { return "Strategy B" }
type Context struct { strategy Strategy }
func (c *Context) SetStrategy(strategy Strategy) { c.strategy = strategy }
func (c *Context) ExecuteStrategy() string { return c.strategy.Execute() }The UML diagram shows the Strategy interface, concrete strategies, and the Context that delegates execution.
4. Observer Pattern
The Observer defines a one‑to‑many dependency so that when the subject changes state, all its observers are automatically notified. Go implements this with a subject struct that maintains a slice of observer interfaces and methods to attach and notify observers.
package observer
type Subject struct { observers []Observer }
func (s *Subject) Attach(o Observer) { s.observers = append(s.observers, o) }
func (s *Subject) Notify() {
for _, observer := range s.observers {
observer.Update()
}
}
type Observer interface { Update() }
type ConcreteObserverA struct{}
func (c *ConcreteObserverA) Update() { /* specific logic */ }
type ConcreteObserverB struct{}
func (c *ConcreteObserverB) Update() { /* specific logic */ }The UML diagram visualizes the subject‑observer relationship.
These four Go implementations demonstrate how classic design patterns can be adapted to a language without traditional classes or inheritance, leveraging interfaces and struct composition to achieve modular, maintainable, and extensible code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Ops Development & AI Practice
DevSecOps engineer sharing experiences and insights on AI, Web3, and Claude code development. Aims to help solve technical challenges, improve development efficiency, and grow through community interaction. Feel free to comment and discuss.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
