Ответ
В Go, с его упором на простоту и композицию, многие классические паттерны реализуются идиоматично, часто без сложных иерархий наследования. Наиболее часто я использую:
-
Фабричный метод (Factory Method) - создание объектов через специальные функции-конструкторы. Это позволяет скрыть сложную логику инициализации и отделить клиента от конкретных типов.
// NewLogger - это фабричная функция func NewLogger(output io.Writer) *Logger { return &Logger{output: output} }
-
Одиночка (Singleton) - гарантирует, что у класса есть только один экземпляр. В Go идиоматично и безопасно реализуется через
sync.Once
для глобальных объектов, таких как пулы соединений или конфигурация.var instance *Database var once sync.Once func GetDB() *Database { once.Do(func() { instance = connectDB() // Эта функция выполнится только один раз }) return instance }
-
Внедрение зависимостей (Dependency Injection) - зависимости передаются в структуру извне (обычно через конструктор), а не создаются внутри нее. Это ключевой паттерн для создания тестируемого и модульного кода.
type Service struct { repo Repository // Зависимость от интерфейса, а не от конкретной реализации } func NewService(r Repository) *Service { return &Service{repo: r} }
-
Декоратор (Decorator) - позволяет динамически добавлять новую функциональность объектам, оборачивая их. В Go этот паттерн часто используется для
http.Handler
(middleware) или для оборачивания интерфейсов.// LoggingMiddleware - декоратор для http.Handler func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Println(r.URL.Path) next.ServeHTTP(w, r) }) }
Также в Go часто применяются:
- Стратегия (Strategy): Реализуется через интерфейсы или функции высшего порядка.
- Адаптер (Adapter): Естественно ложится на "утиную типизацию" Go через интерфейсы.
- Наблюдатель (Observer): Часто реализуется с помощью каналов для оповещения подписчиков о событиях.