Design Patterns in Go: Examples and Practical Application

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

Introduction: Why Design Patterns Matter in Go



Go (Golang) is a modern compiled language created at Google for solving high-load system problems. Despite its simplicity and conciseness, developing in Go requires an understanding of design patterns — proven solutions to typical tasks. In this article, we will break down the main patterns with code examples that will help you write cleaner, more scalable, and maintainable code.



Why is this important? Go does not support classical OOP (inheritance, method overloading), but it offers powerful mechanisms — interfaces, composition, and goroutines. Patterns adapt to these features, making them particularly effective.



Creational Patterns



Creational patterns manage the process of object creation. In Go, they are often implemented through constructor functions and interfaces.



1. Singleton



Ensures that a class has only one instance and provides a global point of access to it. In Go, this is especially relevant for configurations, loggers, and connection pools.



package main

import ( "fmt" "sync")

type Config struct { Port int Host string}

var ( instance *Config once sync.Once)

func GetConfig() *Config { once.Do(func() { instance = &Config{ Port: 8080, Host: "localhost", } }) return instance}

func main() { cfg1 := GetConfig() cfg2 := GetConfig() fmt.Println(cfg1 == cfg2) // true}


Explanation: sync.Once ensures that initialization happens only once, even under concurrent access. This is the standard idiomatic way to implement Singleton in Go.



2. Factory Method



Defines an interface for creating an object but lets subclasses alter the type of objects that will be created. In Go, it is implemented through functions that return an interface.



package main

import "fmt"

// Product interfacetype Transport interface { Deliver() string}

// Concrete productstype Truck struct{}func (t *Truck) Deliver() string { return "Delivery by land" }

type Ship struct{}func (s *Ship) Deliver() string { return "Delivery by sea" }

// Factory methodfunc CreateTransport(transportType string) Transport { switch transportType { case "land": return &Truck{} case "sea": return &Ship{} default: return nil }}

func main() { transport := CreateTransport("sea") fmt.Println(transport.Deliver()) // Delivery by sea}


3. Builder



Allows creating complex objects step by step. It is ideal for objects with many optional parameters.



package main

import "fmt"

type Pizza struct { Dough string Sauce string Toppings []string}

type PizzaBuilder struct { dough string sauce string toppings []string}

func NewPizzaBuilder() *PizzaBuilder { return &PizzaBuilder{}}

func (b *PizzaBuilder) SetDough(dough string) *PizzaBuilder { b.dough = dough return b}

func (b *PizzaBuilder) SetSauce(sauce string) *PizzaBuilder { b.sauce = sauce return b}

func (b *PizzaBuilder) AddTopping(topping string) *PizzaBuilder { b.toppings = append(b.toppings, topping) return b}

func (b *PizzaBuilder) Build() *Pizza { return &Pizza{ Dough: b.dough, Sauce: b.sauce, Toppings: b.toppings, }}

func main() { pizza := NewPizzaBuilder(). SetDough("thin"). SetSauce("tomato"). AddTopping("cheese"). AddTopping("mushrooms"). Build() fmt.Printf("Pizza: %+v", pizza)}


St

Blogs

Book Recommendations