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

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

1. Основное правило

Интерфейс должен определяться в пакете, который его использует, а не в пакете, который предоставляет реализацию. Это следует из:

  • Принципа инверсии зависимостей (DIP)
  • Идиомы Go "Accept interfaces, return structs"

Пример правильного подхода:

// Пакет client (потребитель)
package client

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

func ProcessData(s Storage, id string) error {
    // использует интерфейс
}

// Пакет storage (реализация)
package storage

type DiskStorage struct{...}

func (d *DiskStorage) Get(id string) ([]byte, error) {...}
func (d *DiskStorage) Put(id string, data []byte) error {...}

2. Когда можно размещать интерфейс в пакете с реализацией

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

  1. Стандартные интерфейсы (например, io.Reader)
  2. Когда интерфейс является основной сущностью пакета
  3. Для обратной совместимости

Пример из стандартной библиотеки:

// Пакет io определяет интерфейсы
package io

type Reader interface {
    Read(p []byte) (n int, err error)
}

3. Почему важно правильное расположение

Проблемы при размещении в пакете реализации:

  1. Жесткая связность: Потребитель зависит от конкретной реализации
  2. Трудности тестирования: Сложно подменять реализации
  3. Нарушение SRP: Пакет отвечает и за реализацию, и за контракт

Преимущества размещения у потребителя:

  1. Гибкость: Легко добавить новые реализации
  2. Тестируемость: Просто использовать моки
  3. Чистые зависимости: Потребитель определяет, что ему нужно

4. Практические рекомендации

  1. Начинайте с размещения интерфейса у потребителя
  2. Переносите в пакет реализации только при явной необходимости
  3. Для общеиспользуемых интерфейсов создавайте отдельные пакеты
  4. Избегайте "интерфейсного загрязнения" - создавайте минимальные интерфейсы

Резюмируем

  1. Основное правило: Интерфейсы определяет потребитель
  2. Исключения: Стандартные контракты и особые случаи
  3. Выгоды: Гибкость, тестируемость, чистая архитектура
  4. Антипаттерн: Большие интерфейсы в пакетах реализации

Правильное расположение интерфейсов делает код более поддерживаемым и соответствует философии Go.