Fundamentals 5 min read

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.

Ops Development & AI Practice
Ops Development & AI Practice
Ops Development & AI Practice
Mastering Go Design Patterns: Singleton, Factory, Strategy, and Observer Explained

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.

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.

Design PatternsGoSingletonstrategyFactoryObserver
Ops Development & AI Practice
Written by

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.

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.