Ответ
В Go действует принцип: «Принимай интерфейсы, возвращай структуры» (Accept interfaces, return structs).
Исходя из этого, интерфейсы следует определять в пакете, который их использует (потребитель/consumer), а не в пакете, который их реализует (поставщик/producer).
Этот подход дает несколько ключевых преимуществ:
- Отсутствие циклических зависимостей: Если бы пакет
A
реализовывал интерфейс из пакетаB
, а пакетB
использовал бы тип из пакетаA
, возникла бы циклическая зависимость, которую компилятор Go не допустит. - Слабая связанность (Decoupling): Пакет-потребитель определяет только те методы, которые ему действительно нужны. Он не зависит от всего API пакета-поставщика, а только от необходимого контракта.
- Ясность API пакета: Пакет-потребитель явно декларирует свои зависимости, что делает код более понятным и легким для тестирования (легко создать mock-реализацию).
Пример правильного размещения:
Предположим, у нас есть сервис пользователей (users
), который зависит от хранилища (storage
).
// Пакет storage (поставщик) - предоставляет конкретную реализацию
package storage
import "fmt"
// Конкретный тип, который будет реализовывать интерфейс.
// Обратите внимание, он ничего не знает об интерфейсе. ype DBStore struct {
// ...поля для подключения к БД
}
func (s *DBStore) GetUserByID(id int) (string, error) {
// Логика получения пользователя из БД
return fmt.Sprintf("User %d", id), nil
}
// Пакет users (потребитель) - определяет и использует интерфейс
package users
// Интерфейс определяется здесь, в пакете-потребителе.
// Он описывает только то, что нужно сервису `users`.
type UserStorage interface {
GetUserByID(id int) (string, error)
}
// Сервис зависит от интерфейса, а не от конкретной реализации `storage.DBStore`.
type Service struct {
storage UserStorage
}
func NewService(s UserStorage) *Service {
return &Service{storage: s}
}
func (s *Service) GetUserName(id int) (string, error) {
return s.storage.GetUserByID(id)
}
Анти-паттерн: Создавать один большой пакет interfaces
, куда сваливаются все интерфейсы проекта. Это нарушает принцип принадлежности интерфейса его потребителю и часто приводит к созданию "раздутых" интерфейсов.