Ответ
В Go существует идиома: «Интерфейсы принадлежат потребителю». Это означает, что интерфейсы следует объявлять в пакете, который их использует, а не в пакете, который их реализует.
Почему это важно?
Уменьшение связанности (Decoupling): Пакет, предоставляющий реализацию, не должен знать о своих потребителях. Это предотвращает циклические зависимости и делает компоненты системы более независимыми. Потребитель определяет контракт, который ему нужен, а реализатор его выполняет.
Принцип разделения интерфейсов (Interface Segregation Principle): Потребитель определяет только тот набор методов, который ему действительно необходим. Это приводит к созданию маленьких, сфокусированных интерфейсов, вместо одного большого и громоздкого.
Простота тестирования и мокирования: Когда интерфейс находится в пакете-потребителе, для его тестирования легко создать заглушку (mock), не импортируя при этом пакет с реальной, возможно, тяжеловесной реализацией.
Пример:
Предположим, у нас есть пакет notifier
, который отправляет уведомления, и пакет database
для работы с БД.
// Пакет notifier (потребитель)
// Он не знает о существовании database, ему нужен лишь кто-то,
// кто может предоставить данные пользователя.
package notifier
// UserFinder - интерфейс, определенный потребителем.
type UserFinder interface {
FindUserByID(id int) (string, error)
}
func NotifyUser(finder UserFinder, userID int) {
userName, err := finder.FindUserByID(userID)
// ... логика отправки уведомления
}
// Пакет database (реализатор)
// Этот пакет ничего не знает об интерфейсе UserFinder.
// Он просто предоставляет конкретный тип с нужным методом.
package database
type PostgresDB struct { /* ... */ }
// Метод полностью соответствует сигнатуре интерфейса UserFinder.
func (db *PostgresDB) FindUserByID(id int) (string, error) {
// ... логика поиска пользователя в БД
return "John Doe", nil
}
Такой подход делает код более гибким, модульным и легко поддерживаемым.