Паттерны проектирования в Go: примеры и практическое применение

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему паттерны проектирования важны в Go

Go (Golang) — это современный компилируемый язык, созданный в Google для решения задач высоконагруженных систем. Несмотря на свою простоту и лаконичность, разработка на Go требует понимания паттернов проектирования — проверенных решений типовых задач. В этой статье мы разберем основные паттерны с примерами кода, которые помогут вам писать более чистый, масштабируемый и поддерживаемый код.

Почему это важно? Go не поддерживает классическое ООП (наследование, перегрузку методов), но предлагает мощные механизмы — интерфейсы, композицию и горутины. Паттерны адаптируются под эти особенности, что делает их особенно эффективными.

Порождающие паттерны (Creational)

Порождающие паттерны управляют процессом создания объектов. В Go они часто реализуются через функции-конструкторы и интерфейсы.

1. Singleton (Одиночка)

Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В Go это особенно актуально для конфигураций, логгеров и пулов соединений.

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
}

Объяснение: sync.Once гарантирует, что инициализация произойдет только один раз, даже при конкурентном доступе. Это стандартный идиоматический способ реализации Singleton в Go.

2. Factory Method (Фабричный метод)

Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемых объектов. В Go реализуется через функции, возвращающие интерфейс.

package main

import "fmt"

// Интерфейс продукта
type Transport interface {
    Deliver() string
}

// Конкретные продукты
type Truck struct{}
func (t *Truck) Deliver() string { return "Доставка по земле" }

type Ship struct{}
func (s *Ship) Deliver() string { return "Доставка по морю" }

// Фабричный метод
func 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()) // Доставка по морю
}

3. Builder (Строитель)

Позволяет создавать сложные объекты пошагово. Идеально подходит для объектов с множеством опциональных параметров.

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("тонкое").
        SetSauce("томатный").
        AddTopping("сыр").
        AddTopping("грибы").
        Build()
    fmt.Printf("Пицца: %+v\n", pizza)
}

Структурные паттерны (Structural)

Структурные паттерны определяют способы сборки объектов и классов в более крупные структуры. В Go они часто используют композицию и интерфейсы.

4. Adapter (Адаптер)

Позволяет объектам с несовместимыми интерфейсами работать вместе. Классический пример — интеграция сторонних библиотек.

package main

import "fmt"

// Целевой интерфейс
type JSONSerializer interface {
    Serialize(data interface{}) string
}

// Сторонний класс с другим интерфейсом
type XMLSerializer struct{}

func (x *XMLSerializer) ConvertToXML(data interface{}) string {
    return "<xml>data</xml>"
}

// Адаптер
type XMLToJSONAdapter struct {
    xmlSerializer *XMLSerializer
}

func (a *XMLToJSONAdapter) Serialize(data interface{}) string {
    xml := a.xmlSerializer.ConvertToXML(data)
    // Имитация конвертации XML в JSON
    return `{"data": "` + xml + `"}`
}

func main() {
    adapter := &XMLToJSONAdapter{xmlSerializer: &XMLSerializer{}}
    fmt.Println(adapter.Serialize("test"))
}

5. Composite (Компоновщик)

Позволяет сгруппировать объекты в древовидную структуру и работать с ними как с единым целым. Широко применяется в UI-компонентах и файловых системах.

package main

import "fmt"

// Компонент
type FileSystemNode interface {
    GetSize() int
    Print(indent string)
}

// Лист
type File struct {
    Name string
    Size int
}

func (f *File) GetSize() int { return f.Size }

func (f *File) Print(indent string) {
    fmt.Printf("%sФайл: %s (%d байт)\n", indent, f.Name, f.Size)
}

// Контейнер
type Directory struct {
    Name     string
    Children []FileSystemNode
}

func (d *Directory) GetSize() int {
    total := 0
    for _, child := range d.Children {
        total += child.GetSize()
    }
    return total
}

func (d *Directory) Print(indent string) {
    fmt.Printf("%sДиректория: %s\n", indent, d.Name)
    for _, child := range d.Children {
        child.Print(indent + "  ")
    }
}

func main() {
    root := &Directory{Name: "root"}
    root.Children = append(root.Children, &File{Name: "file1.txt", Size: 100})
    
    subDir := &Directory{Name: "subdir"}
    subDir.Children = append(subDir.Children, &File{Name: "file2.txt", Size: 200})
    root.Children = append(root.Children, subDir                

Блоги

Рекомендаций по книгам