Введение: Почему паттерны проектирования важны в 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