Паттерны проектирования в Go (Golang) с примерами кода
Паттерны проектирования — это проверенные решения типовых задач разработки. В Go, с его лаконичным синтаксисом и отсутствием классического ООП, многие паттерны обретают уникальную реализацию. В этой статье мы разберём три группы паттернов: порождающие, структурные и поведенческие — с практическими примерами на Golang.
Материал будет полезен как новичкам, так и опытным Go-разработчикам, стремящимся писать чистый, переиспользуемый и тестируемый код.
1. Порождающие паттерны (Creational Patterns)
Порождающие паттерны управляют процессом создания объектов. В Go, где нет конструкторов и наследования, их реализация особенно изящна.
1.1. Одиночка (Singleton)
Гарантирует, что у типа есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В Go часто реализуется через sync.Once.
package singleton
import (
"sync"
)
type Database struct {
connection string
}
var (
instance *Database
once sync.Once
)
func GetDatabase() *Database {
once.Do(func() {
instance = &Database{connection: "postgres://user:pass@localhost/db"}
})
return instance
}
Когда использовать: работа с пулом соединений, кэшем, логгером.
1.2. Фабричный метод (Factory Method)
Определяет интерфейс для создания объекта, но позволяет подклассам изменять тип создаваемого объекта. В Go фабричный метод — это просто функция, возвращающая интерфейс.
package factory
import "fmt"
// Product interface
type INotification interface {
Send(message string) string
}
// Concrete products
type Email struct{}
func (e *Email) Send(msg string) string {
return fmt.Sprintf("Email sent: %s", msg)
}
type SMS struct{}
func (s *SMS) Send(msg string) string {
return fmt.Sprintf("SMS sent: %s", msg)
}
// Factory function
func CreateNotification(notifType string) INotification {
switch notifType {
case "email":
return &Email{}
case "sms":
return &SMS{}
default:
return nil
}
}
Когда использовать: когда тип создаваемого объекта неизвестен заранее.
1.3. Строитель (Builder)
Отделяет конструирование сложного объекта от его представления. В Go строитель — это структура с методами-сеттерами, возвращающими самого себя для цепочечного вызова.
package builder
type Pizza struct {
Size string
Cheese bool
Pepperoni bool
}
type PizzaBuilder struct {
pizza Pizza
}
func NewPizzaBuilder() *PizzaBuilder {
return &PizzaBuilder{}
}
func (b *PizzaBuilder) SetSize(size string) *PizzaBuilder {
b.pizza.Size = size
return b
}
func (b *PizzaBuilder) AddCheese() *PizzaBuilder {
b.pizza.Cheese = true
return b
}
func (b *PizzaBuilder) AddPepperoni() *PizzaBuilder {
b.pizza.Pepperoni = true
return b
}
func (b *PizzaBuilder) Build() Pizza {
return b.pizza
}
Когда использовать: когда объект имеет много опциональных параметров.
2. Структурные паттерны (Structural Patterns)
Структурные паттерны определяют способы сборки объектов и классов в более крупные структуры, сохраняя гибкость и эффективность.
2.1. Адаптер (Adapter)
Позволяет объектам с несовместимыми интерфейсами работать вместе. В Go для этого используются интерфейсы и структуры-обёртки.
package adapter
import "fmt"
// Target interface
type USB interface {
ConnectWithUSB() string
}
// Adaptee (несовместимый класс)
type MemoryCard struct{}
func (m *MemoryCard) ReadFromCard() string {
return "Data read from memory card"
}
// Adapter
type CardReader struct {
card *MemoryCard
}
func NewCardReader(card *MemoryCard) *CardReader {
return &CardReader{card: card}
}
func (r *CardReader) ConnectWithUSB() string {
return fmt.Sprintf("Adapter: %s via USB", r.card.ReadFromCard())
}
Когда использовать: интеграция с legacy-кодом или сторонними библиотеками.
2.2. Декоратор (Decorator)
Динамически добавляет объекту новую функциональность, оборачивая его в полезные обёртки. В Go декораторы реализуются через композицию интерфейсов.
package decorator
import "fmt"
type INotifier interface {
Send(message string) string
}
type Notifier struct{}
func (n *Notifier) Send(msg string) string {
return fmt.Sprintf("Sending: %s", msg)
}
// Decorator 1
type SMSNotifier struct {
notifier INotifier
}
func (s *SMSNotifier) Send(msg string) string {
return s.notifier.Send(msg) + " via SMS"
}
// Decorator 2
type SlackNotifier struct {
notifier INotifier
}
func (s *SlackNotifier) Send(msg string) string {
return s.notifier.Send(msg) + " via Slack"
}
Когда использовать: когда нужно добавлять обязанности динамически и в разных комбинациях.
3. Поведенческие паттерны (Behavioral Patterns)
Поведенческие паттерны решают задачи эффективного взаимодействия между объектами.
3.1. Наблюдатель (Observer)
Создаёт механизм подписки, позволяющий одним объектам следить и реагировать на события, происходящие в других объектах. В Go часто используют каналы.
package observer
import "fmt"
type Subject struct {
observers []chan string
}
func (s *Subject) Attach(observer chan string) {
s.observers = append(s.observers, observer)
}
func (s *Subject) Notify(msg string) {
for _, observer := range s.observers {
observer <- msg
}
}
// Использование
func ExampleObserver() {
subject := &Subject{}
obs1 := make(chan string, 1)
obs2 := make(chan string, 1)
subject.Attach(obs1)
subject.Attach(obs2)
subject.Notify("Event happened!")
fmt.Println(<-obs1) // Event happened!
fmt.Println(<-obs2) // Event happened!
}
Когда использовать: системы событий, уведомления, обновление UI.
3.2. Стратегия (Strategy)
Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. В Go стратегия — это просто интерфейс с одним методом.
<