Вопрос расположения интерфейсов в кодовой базе 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. Когда можно размещать интерфейс в пакете с реализацией
Исключения из основного правила:
- Стандартные интерфейсы (например,
io.Reader
)
- Когда интерфейс является основной сущностью пакета
- Для обратной совместимости
Пример из стандартной библиотеки:
// Пакет io определяет интерфейсы
package io
type Reader interface {
Read(p []byte) (n int, err error)
}
3. Почему важно правильное расположение
Проблемы при размещении в пакете реализации:
- Жесткая связность: Потребитель зависит от конкретной реализации
- Трудности тестирования: Сложно подменять реализации
- Нарушение SRP: Пакет отвечает и за реализацию, и за контракт
Преимущества размещения у потребителя:
- Гибкость: Легко добавить новые реализации
- Тестируемость: Просто использовать моки
- Чистые зависимости: Потребитель определяет, что ему нужно
4. Практические рекомендации
- Начинайте с размещения интерфейса у потребителя
- Переносите в пакет реализации только при явной необходимости
- Для общеиспользуемых интерфейсов создавайте отдельные пакеты
- Избегайте "интерфейсного загрязнения" - создавайте минимальные интерфейсы
Резюмируем
- Основное правило: Интерфейсы определяет потребитель
- Исключения: Стандартные контракты и особые случаи
- Выгоды: Гибкость, тестируемость, чистая архитектура
- Антипаттерн: Большие интерфейсы в пакетах реализации
Правильное расположение интерфейсов делает код более поддерживаемым и соответствует философии Go.