Какие паттерны проектирования вы использовали в Go? Расскажите о них.

Ответ

В Go, с его упором на простоту и композицию, многие классические паттерны реализуются идиоматично, часто без сложных иерархий наследования. Наиболее часто я использую:

  1. Фабричный метод (Factory Method) - создание объектов через специальные функции-конструкторы. Это позволяет скрыть сложную логику инициализации и отделить клиента от конкретных типов.

    // NewLogger - это фабричная функция
    func NewLogger(output io.Writer) *Logger {
        return &Logger{output: output}
    }
  2. Одиночка (Singleton) - гарантирует, что у класса есть только один экземпляр. В Go идиоматично и безопасно реализуется через sync.Once для глобальных объектов, таких как пулы соединений или конфигурация.

    var instance *Database
    var once sync.Once
    
    func GetDB() *Database {
        once.Do(func() {
            instance = connectDB() // Эта функция выполнится только один раз
        })
        return instance
    }
  3. Внедрение зависимостей (Dependency Injection) - зависимости передаются в структуру извне (обычно через конструктор), а не создаются внутри нее. Это ключевой паттерн для создания тестируемого и модульного кода.

    type Service struct {
        repo Repository // Зависимость от интерфейса, а не от конкретной реализации
    }
    
    func NewService(r Repository) *Service {
        return &Service{repo: r}
    }
  4. Декоратор (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): Часто реализуется с помощью каналов для оповещения подписчиков о событиях.