Паттерны проектирования в Go с примерами: от А до Я
Паттерны проектирования (Design Patterns) — это проверенные временем архитектурные решения, которые помогают разработчикам Go создавать гибкий, масштабируемый и поддерживаемый код. В отличие от классических языков ООП, Go предлагает свой уникальный подход к реализации шаблонов через интерфейсы, композицию и горутины. В этой статье мы разберем основные паттерны проектирования в Go с конкретными примерами кода.
1. Порождающие паттерны (Creational Patterns)
Порождающие паттерны отвечают за создание объектов, делая систему независимой от способа их создания.
1.1. Singleton (Одиночка)
Синглтон гарантирует, что у класса будет только один экземпляр. В Go это часто реализуется через sync.Once для потокобезопасности.
package main
import (
"fmt"
"sync"
)
type Database struct {
connection string
}
var (
instance *Database
once sync.Once
)
func GetDatabaseInstance() *Database {
once.Do(func() {
instance = &Database{connection: "postgres://localhost:5432/mydb"}
fmt.Println("Создано единственное подключение к БД")
})
return instance
}
func main() {
db1 := GetDatabaseInstance()
db2 := GetDatabaseInstance()
fmt.Println(db1 == db2) // true
}
1.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 "Доставка по морю" }
// Фабрика
type LogisticsFactory interface {
CreateTransport() Transport
}
type RoadLogistics struct{}
func (r RoadLogistics) CreateTransport() Transport { return Truck{} }
type SeaLogistics struct{}
func (s SeaLogistics) CreateTransport() Transport { return Ship{} }
func main() {
var factory LogisticsFactory = RoadLogistics{}
transport := factory.CreateTransport()
fmt.Println(transport.Deliver()) // Доставка по суше
}
2. Структурные паттерны (Structural Patterns)
Структурные паттерны помогают организовать классы и объекты в более крупные структуры.
2.1. Adapter (Адаптер)
Адаптер позволяет объектам с несовместимыми интерфейсами работать вместе. В Go это решается через обертку.
package main
import "fmt"
// Старый интерфейс
type OldPrinter interface {
PrintOld(data string)
}
type LegacyPrinter struct{}
func (l LegacyPrinter) PrintOld(data string) {
fmt.Println("Legacy:", data)
}
// Новый интерфейс
type NewPrinter interface {
PrintNew(data string)
}
// Адаптер
type PrinterAdapter struct {
OldPrinter OldPrinter
}
func (a PrinterAdapter) PrintNew(data string) {
a.OldPrinter.PrintOld(data)
}
func main() {
legacy := LegacyPrinter{}
adapter := PrinterAdapter{OldPrinter: legacy}
adapter.PrintNew("Hello, Go!") // Legacy: Hello, Go!
}
2.2. Composite (Компоновщик)
Компоновщик позволяет обрабатывать группы объектов как единый объект. Идеально подходит для древовидных структур.
package main
import "fmt"
// Интерфейс компонента
type Graphic interface {
Draw() string
}
// Листовой элемент
type Circle struct{}
func (c Circle) Draw() string { return "Рисуем круг" }
// Контейнер
type CompoundGraphic struct {
children []Graphic
}
func (cg *CompoundGraphic) Add(g Graphic) {
cg.children = append(cg.children, g)
}
func (cg *CompoundGraphic) Draw() string {
result := "Составная фигура:\n"
for _, child := range cg.children {
result += " - " + child.Draw() + "\n"
}
return result
}
func main() {
circle1 := Circle{}
circle2 := Circle{}
group := &CompoundGraphic{}
group.Add(circle1)
group.Add(circle2)
fmt.Println(group.Draw())
}
3. Поведенческие паттерны (Behavioral Patterns)
Поведенческие паттерны определяют алгоритмы и взаимодействия между объектами.
3.1. Strategy (Стратегия)
Стратегия позволяет менять алгоритм на лету. В Go это реализуется через интерфейсы.
package main
import "fmt"
// Интерфейс стратегии
type PaymentStrategy interface {
Pay(amount float64) string
}
// Конкретные стратегии
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Оплачено %.2f кредитной картой", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Оплачено %.2f через PayPal", amount)
}
// Контекст
type ShoppingCart struct {
payment PaymentStrategy
}
func (s *ShoppingCart) SetPayment(p PaymentStrategy) {
s.payment = p
}
func (s ShoppingCart) Checkout(amount float64) {
fmt.Println(s.payment.Pay(amount))
}
func main() {
cart := ShoppingCart{}
cart.SetPayment(CreditCard{})
cart.Checkout(100.50)
cart.SetPayment(PayPal{})
cart.Checkout(200.75)
}
3.2. Observer (Наблюдатель)
Наблюдатель уведомляет подписчиков об изменениях. В Go удобно использовать каналы.
package main
import "fmt"
// Интерфейс наблюдателя
type Observer interface {
Update(message string)
}
// Конкретный наблюдатель
type User struct {
name string
}
func (u User) Update(message string) {
fmt.Printf("Пользователь %s получил: %s\n", u.name, message)
}
// Издатель
type NewsPublisher struct {
observers []Observer
}
func (np *NewsPublisher) Subscribe(o Observer) {
np.observers = append(np.observers, o)
}
func (np *NewsPublisher) NotifyAll(message string) {
for _, observer := range np.observers {
observer.Update(message)
}
}
func main() {
publisher := &NewsPublisher{}
user1 := User{name: "Анна"}
user2 := User{name: "Иван"}
publisher.Subscribe(user1)
publisher.Subscribe(user2)
publisher.NotifyAll("Новая статья по Go!")
}
Заключение
Паттерны проектирования в Go — это мощный инструмент, который помогает писать чистый и модульный код. В отличие от классического ООП, Go предлагает более простые и элегантные решения: