Fundamentals 13 min read

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.

Code Wrench
Code Wrench
Code Wrench
Master Go Behavioral Design Patterns: Strategy, Observer, Command & More

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.
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Golangbehavioral-patternsdesign-patternssoftware-architecture
Code Wrench
Written by

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. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.