Где следует поместить описание интерфейса: в пакете с реализацией или в пакете, где этот интерфейс используется? Почему?go-86

Этот вопрос касается архитектурного принципа зависимости от абстракций и напрямую влияет на:

  • Гибкость кода
  • Тестируемость
  • Связность модулей

Основные подходы

1. Интерфейс в пакете потребителя

Рекомендуемый подход в Go согласно философии "интерфейсы должны быть там, где они используются".

Пример:

// consumer/processor.go
package consumer

type Storage interface {
    Get(id string) ([]byte, error)
    Put(id string, data []byte) error
}

func ProcessData(s Storage, id string) error {
    data, err := s.Get(id)
    // ...
}

Преимущества:

  • Потребитель определяет только нужные ему методы
  • Легкая подмена реализаций для тестирования (mock)
  • Избегает ненужных зависимостей
  • Соответствует Dependency Inversion Principle (SOLID)

2. Интерфейс в пакете реализации

Традиционный подход из других языков, но менее гибкий в Go:

// storage/storage.go
package storage

type Storage interface {
    Get(id string) ([]byte, error)
    Put(id string, data []byte) error
    // +10 методов, которые могут не нужны потребителю
}

type FileStorage struct{...}

func New() Storage {
    return &FileStorage{}
}

Проблемы:

  • Жесткая привязка к конкретной реализации
  • Интерфейс "раздувается" всеми методами реализации
  • Сложнее тестировать (нужно либо реализовывать весь интерфейс, либо использовать тяжелые моки)

Исключения из правила

  1. Стандартные интерфейсы (io.Reader, http.Handler) - когда нужно соблюдение контракта
  2. Библиотечный код - когда автор библиотеки хочет явно указать доступные абстракции
  3. Несколько потребителей с одинаковыми требованиями к интерфейсу

Практический пример в Go

// Потребитель определяет минимальный интерфейс
package notifier

type AlertSender interface {
    Send(msg string) error
}

func CheckAndNotify(sender AlertSender) {
    // использует только Send()
}

// Реализация в отдельном пакете может иметь больше методов
package smsnotifier

type SMSService struct{...}

func (s *SMSService) Send(msg string) error {...}
func (s *SMSService) Configure(params ...) {...} // внутренний метод

Резюмируем

в Go интерфейсы лучше определять в месте использования, чтобы минимизировать зависимости и обеспечить гибкость. Это один из ключевых паттернов идиоматичного Go-кода.