Ответ
В 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
}
Такой подход делает код более гибким, модульным и легко поддерживаемым.
Ответ 18+ 🔞
Да ты посмотри, какая, блядь, хуйня интересная в Go вылезла! Есть там такая идиома, прямо как мантра у них: «Интерфейсы принадлежат потребителю». А переводится это на русский, блядь, так: объявлять интерфейсы надо там, где их юзают, а не там, где их реализуют. Вот так вот, наоборот, блядь!
А че, собственно, за ебушки-воробушки? А вот зачем это надо, слушай сюда:
-
Связанность уменьшить, чтоб её. Чтобы пакет, который делает работу, нихуя не знал о тех, кто его использует. Ну представь, блядь, ты пишешь библиотеку, а от тебя требуют: «А объяви-ка ты интерфейс для того мудака, который тебя будет вызывать». Это ж пиздец, блядь! Циклические зависимости попрут, как тараканы. А так — потребитель сам говорит: «Мне вот такой контракт нужен», а реализатор молча его выполняет. Красота, ёпта!
-
Принцип «Не суй мне всё в один интерфейс». Потребитель — он жадный, но умный. Он не будет требовать от тебя 25 методов, если ему надо всего два. Он и определит интерфейс ровно из этих двух методов. Получаются интерфейсы маленькие, аккуратные, а не этакая манда с ушами на 100500 методов, которую заебёшься реализовывать.
-
Тестирование, блядь, упрощается в разы. Ну вот смотри: интерфейс у тебя в пакете-потребителе. Тебе надо написать тест. И что ты делаешь? Правильно, пишешь заглушку (mock) прямо там же, в тестах, и нихуя не импортируешь тот тяжёлый пакет с реальной базой данных или внешним API. Чих-пых — и тест готов. Удобно же, сука!
Ну и пример, чтобы совсем пиздец стало понятно:
Допустим, есть у нас пакет notifier, который шлёт уведомления, и пакет database, который в базе ковыряется.
// Пакет notifier (это наш потребитель, жадная сволочь)
// Ему похуй на базу данных, ему нужен кто-то, кто даст имя юзера.
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 { /* ... */ }
// Смотри-ка, метод в точности подходит под интерфейс того потребителя!
// Совпадение? Не думаю, блядь!
func (db *PostgresDB) FindUserByID(id int) (string, error) {
// ... тут он лезет в базу, ищет юзера, потеет
return "Василий Петрович", nil
}
И вот теперь, в основном коде, ты просто скормишь notifier.NotifyUser экземпляр PostgresDB, и всё заведётся, потому что Go сам увидит, что методы-то подходят. А если завтра надо будет брать данные не из базы, а из файла или с потолка — ты просто создашь новый тип с таким же методом, и потребитель даже не заметит подмены. Гибко, модульно, и не ебёт мозг. Вот так-то, блядь!