Design Patterns in Go: When and Why to Use Them

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Design Patterns in Go: Not a "Religion," But a Tool

\n\n

When I first started writing in Go, I was haunted by the feeling that all this "Gang of Four" stuff with their 23 patterns was something from the world of Java and C++. Go is so simple and concise — why do we need these complexities? But the deeper I delved into real projects (microservices, CLI utilities, API handlers), the more often I caught myself thinking: "A factory method would come in handy here" or "This mountain of if-else can be replaced with a strategy."

\n\n

Design patterns in Go are not a dogma or an attempt to force OOP onto a functional language. They are proven solutions to typical problems. They help make code testable, flexible, and understandable for colleagues. And yes, Go leaves its mark: due to the lack of classical inheritance and generics (before version 1.18), some patterns are transformed, while others become redundant.

\n\n

In this article, we won't list all 23 patterns. Instead, we'll break down the 5 most in-demand patterns in Golang that will actually be useful in production projects. You'll see when to apply them and when to run away without looking back.

\n\n

The Singleton Pattern in Go: Not Just a Singleton, But a Go-style Singleton

\n\n

Singleton guarantees that a struct will have only one instance. In Go, this is often used for configs, connection pools, or loggers. But there's a nuance: Go doesn't have static classes, but it does have sync.Once — the perfect tool for lazy initialization.

\n\n

The Problem Without Singleton

\n

Imagine you have a Config struct that reads settings from a file. If every package creates its own instance, you'll either read the file 10 times (slow) or get a race condition.

\n\n

The Solution with Singleton

\n
package config\n\nimport (\n    "sync"\n    "encoding/json"\n    "os"\n)\n\ntype Config struct {\n    Port int    `json:"port"`\n    Env  string `json:"env"`\n}\n\nvar (\n    instance *Config\n    once     sync.Once\n)\n\nfunc GetConfig() *Config {\n    once.Do(func() {\n        file, _ := os.Open("config.json")\n        defer file.Close()\n        instance = &Config{}\n        json.NewDecoder(file).Decode(instance)\n    })\n    return instance\n}
\n\n

When to use: when a guaranteed single instance is needed (configs, caches, factories).

\n

When not to: for everything. A Singleton is global state that complicates testing. If you can pass a dependency through a constructor — do it.

\n\n

The Factory Pattern: When Object Creation Gets Complex

\n\n

Factory Method and Abstract Factory are about encapsulating the logic of object creation. In Go, this is especially relevant since we don't have constructors in the classical sense. Instead, we use constructor functions with the New prefix.

\n\n

Example: Creating Notifications

\n

Let's say your system sends notifications via Email, SMS, and Push. Instead of spreading the selection logic throughout the code, we use a factory.

\n\n
package notification\n\nimport "fmt"\n\ntype Notifier interface {\n    Send(message string) error\n}\n\ntype EmailNotifier struct{}\n\nfunc (e EmailNotifier) Send(message string) error {\n    fmt.Println("Sending email:", message)\n    return nil\n}\n\ntype SMSNotifier struct{}\n\nfunc (s SMSNotifier) Send(message string) error {\n    fmt.Println("Sending SMS:", message)\n    return nil\n}\n\nfunc NewNotifier(typ string) Notifier {\n    switch typ {\n    case "email":\n        return EmailNotifier{}\n    case "sms":\n        return SMSNotifier{}\n    default:\n        return nil\n    }\n}
\n\n

When to use: when the creation logic is complex (depends on type, version, config).

\n

When not to: if you only have one type of object. Just write MyStruct{} or NewMyStruct() without a factory.

\n\n

Pa

Blogs

Book Recommendations