Master Go Behavioral Design Patterns: Strategy, Observer, Command & More
Explore nine essential Go behavioral design patterns—Strategy, Observer, Command, State, Chain of Responsibility, Template Method, Iterator, Visitor, Memento, and Interpreter—through clear definitions, core principles, concise Go implementations, and practical use cases that illustrate how to replace complex if‑else logic with clean, extensible architecture.
When a Go system is cluttered with if‑else, switch, or state checks, it signals that object responsibilities are not clearly separated. Building on a previous article about basic design patterns, this guide delves into the behavioral pattern family, presenting definitions, core ideas, Go implementations, and concrete use cases for each pattern.
1️⃣ Strategy – Dynamically Choose Algorithms
Definition: Define a family of algorithms, encapsulate each one, and make them interchangeable.
Core Idea: Algorithm encapsulation + client selects at runtime.
Go implementation
type DiscountStrategy interface {
Discount(price float64) float64
}
type VIPDiscount struct{}
func (d *VIPDiscount) Discount(price float64) float64 { return price * 0.8 }
type RegularDiscount struct{}
func (d *RegularDiscount) Discount(price float64) float64 { return price }
func ApplyDiscount(price float64, strategy DiscountStrategy) float64 {
return strategy.Discount(price)
}Use case : An e‑commerce system applies different discount strategies based on user type (VIP vs. regular).
Benefit: Adding new strategies requires no client changes, adhering to the Open/Closed Principle.
2️⃣ Observer – Event‑Driven Notification
Definition: One‑to‑many dependency; when a subject changes, all observers are notified.
Core Idea: Publish‑subscribe + dynamic registration.
Go implementation
type Observer interface {
Update(event string)
}
type Subject struct {
observers []Observer
}
func (s *Subject) Attach(o Observer) { s.observers = append(s.observers, o) }
func (s *Subject) Notify(event string) {
for _, o := range s.observers { o.Update(event) }
}
// Asynchronous version using goroutine
func (s *Subject) NotifyAsync(event string) {
for _, o := range s.observers { go o.Update(event) }
}Use case : In a messaging system, order status changes trigger notifications to payment, inventory, and user‑push services.
Go's goroutine makes observer notifications efficient and asynchronous.
3️⃣ Command – Encapsulate Requests as Objects
Definition: Wrap a request as an object, allowing parameterization of clients with different requests, queues, or logs.
Core Idea: Request encapsulation + decouple sender and receiver.
Go implementation
type Command interface { Execute() }
type Light struct{}
func (l *Light) On() { fmt.Println("灯打开") }
func (l *Light) Off() { fmt.Println("灯关闭") }
type LightOnCommand struct { light *Light }
func (c *LightOnCommand) Execute() { c.light.On() }
type Remote struct { command Command }
func (r *Remote) Press() { r.command.Execute() }Use case : A smart‑home remote sends commands to turn lights, curtains, or air‑conditioners on/off.
Benefit: Decouples invoker from executor, enabling undo/redo operations.
4️⃣ State – Object Behavior Driven by Internal State
Definition: Allow an object to alter its behavior when its internal state changes.
Core Idea: State encapsulation + behavior delegation.
Go implementation
type State interface { Handle() }
type ConcreteStateA struct{}
func (s *ConcreteStateA) Handle() { fmt.Println("状态A处理逻辑") }
type ConcreteStateB struct{}
func (s *ConcreteStateB) Handle() { fmt.Println("状态B处理逻辑") }
type Context struct { state State }
func (c *Context) SetState(state State) { c.state = state }
func (c *Context) Request() { c.state.Handle() }Use case : E‑commerce order transitions – Payment → Paid → Shipped → Completed.
State pattern lets order objects change behavior with state, avoiding tangled if‑else chains.
5️⃣ Chain of Responsibility – Dynamic Request Handling
Definition: Pass a request along a chain of handlers until one processes it.
Core Idea: Chain processing + decouple sender and receiver.
Go implementation
type Handler interface {
Handle(request string)
SetNext(handler Handler)
}
type BaseHandler struct { next Handler }
func (b *BaseHandler) SetNext(handler Handler) { b.next = handler }
func (b *BaseHandler) Handle(request string) { if b.next != nil { b.next.Handle(request) } }
type AuthHandler struct { BaseHandler }
func (h *AuthHandler) Handle(request string) { fmt.Println("权限校验"); h.BaseHandler.Handle(request) }
type LogHandler struct { BaseHandler }
func (h *LogHandler) Handle(request string) { fmt.Println("日志记录"); h.BaseHandler.Handle(request) }Use case : Approval workflow – Department manager → Finance → CEO.
New handlers can be added dynamically without modifying existing logic.
6️⃣ Template Method – Define Algorithm Skeleton
Definition: Define the skeleton of an algorithm in a base class, deferring some steps to subclasses.
Core Idea: Fixed workflow + variable steps.
Go implementation
type Game interface {
Initialize()
StartPlay()
EndPlay()
Play()
}
type Cricket struct{}
func (c *Cricket) Initialize() { fmt.Println("Cricket 初始化") }
func (c *Cricket) StartPlay() { fmt.Println("Cricket 开始") }
func (c *Cricket) EndPlay() { fmt.Println("Cricket 结束") }
func (c *Cricket) Play() {
c.Initialize()
c.StartPlay()
c.EndPlay()
}Use case : Game frameworks or task processing pipelines where the overall flow is fixed but specific steps vary per subclass.
7️⃣ Iterator – Traversing Collections
Definition: Provide a way to access elements of a collection sequentially without exposing its underlying representation.
Core Idea: Decouple traversal from storage.
Go implementation
type Iterator interface {
HasNext() bool
Next() interface{}
}
type Collection struct { items []interface{} }
func (c *Collection) Iterator() Iterator { return &collectionIterator{c.items, 0} }
type collectionIterator struct {
items []interface{}
index int
}
func (i *collectionIterator) HasNext() bool { return i.index < len(i.items) }
func (i *collectionIterator) Next() interface{} {
item := i.items[i.index]
i.index++
return item
}Use case : Iterate over order lists, message queues, or log entries without the client needing to know the collection's internal structure.
Clients remain unaware of the collection's concrete implementation.
8️⃣ Visitor – Separate Operations from Object Structure
Definition: Represent an operation to be performed on elements of an object structure without changing the classes of the elements.
Core Idea: Operation encapsulation + object structure stays unchanged.
Go implementation
type Element interface { Accept(v Visitor) }
type Visitor interface {
VisitA(a *ConcreteElementA)
VisitB(b *ConcreteElementB)
}
type ConcreteElementA struct{}
func (e *ConcreteElementA) Accept(v Visitor) { v.VisitA(e) }
type ConcreteElementB struct{}
func (e *ConcreteElementB) Accept(v Visitor) { v.VisitB(e) }
type ConcreteVisitor struct{}
func (v *ConcreteVisitor) VisitA(a *ConcreteElementA) { fmt.Println("访问A") }
func (v *ConcreteVisitor) VisitB(b *ConcreteElementB) { fmt.Println("访问B") }Use case : Performing statistics, export, or reporting on complex data structures without polluting the element classes.
9️⃣ Memento – Capture and Restore Object State
Definition: Capture an object's internal state without violating encapsulation, allowing it to be restored later.
Core Idea: State snapshot + restore mechanism.
Go implementation
type Memento struct { state string }
type Originator struct { state string }
func (o *Originator) SetState(s string) { o.state = s }
func (o *Originator) Save() *Memento { return &Memento{o.state} }
func (o *Originator) Restore(m *Memento) { o.state = m.state }Use case : Undo functionality in a text editor or save‑game feature in a video game.
10️⃣ Interpreter – Evaluate Expressions
Definition: Define a grammar for a language and an interpreter that evaluates sentences in that language.
Core Idea: Syntax tree + interpreter.
Go illustration : Use structs to model simple arithmetic expression nodes and recursively evaluate them.
Ideal for data filtering, conditional queries, or building domain‑specific languages.
Summary
Behavioral patterns address object responsibility division and communication issues.
They reduce if‑else clutter and increase system flexibility.
Go features—interfaces, structs, goroutines, channels—integrate naturally with these patterns, enabling clean, extensible architectures.
The next article will cover structural and architectural patterns, showing how to build clear, scalable systems with Golang.
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.
Code Wrench
Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻
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.
