Ответ
В Go существует общепринятая идиома: интерфейсы следует объявлять в пакете, который их использует (в пакете-потребителе), а не в пакете, который их реализует.
Этот подход основан на нескольких ключевых принципах:
- Уменьшение связанности и избегание циклических зависимостей. Пакет-потребитель определяет, какой функционал ему нужен, и не должен зависеть от пакета с конкретной реализацией. Это позволяет легко заменять реализации.
- Следование принципу инверсии зависимостей (DIP). Высокоуровневые модули (потребители) не должны зависеть от низкоуровневых (реализаций). Оба должны зависеть от абстракций (интерфейсов).
- Гибкость и тестируемость. Позволяет легко подменять реальные зависимости на моки (mocks) или стабы (stubs) в тестах, не изменяя код потребителя.
Пример:
// Хорошо: интерфейс объявлен в пакете-потребителе (consumer)
package consumer
// Storage описывает, что нужно пакету consumer для работы с хранилищем
type Storage interface {
Get(id string) ([]byte, error)
}
func ProcessData(s Storage) { /* ... */ }
// --- В другом пакете находится реализация ---
package db
import "some/path/to/consumer"
// DBStorage реализует интерфейс consumer.Storage
type DBStorage struct{ /* ... */ }
func (db *DBStorage) Get(id string) ([]byte, error) { /* ... */ }
Исключение из правила:
Если интерфейс является частью широко используемого публичного API библиотеки или фреймворка (например, io.Reader
в стандартной библиотеке), его имеет смысл вынести в отдельный, общедоступный пакет.