Где и почему в Go принято объявлять интерфейсы?

Ответ

В Go существует идиома: «Интерфейсы принадлежат потребителю». Это означает, что интерфейсы следует объявлять в пакете, который их использует, а не в пакете, который их реализует.

Почему это важно?

  1. Уменьшение связанности (Decoupling): Пакет, предоставляющий реализацию, не должен знать о своих потребителях. Это предотвращает циклические зависимости и делает компоненты системы более независимыми. Потребитель определяет контракт, который ему нужен, а реализатор его выполняет.

  2. Принцип разделения интерфейсов (Interface Segregation Principle): Потребитель определяет только тот набор методов, который ему действительно необходим. Это приводит к созданию маленьких, сфокусированных интерфейсов, вместо одного большого и громоздкого.

  3. Простота тестирования и мокирования: Когда интерфейс находится в пакете-потребителе, для его тестирования легко создать заглушку (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 вылезла! Есть там такая идиома, прямо как мантра у них: «Интерфейсы принадлежат потребителю». А переводится это на русский, блядь, так: объявлять интерфейсы надо там, где их юзают, а не там, где их реализуют. Вот так вот, наоборот, блядь!

А че, собственно, за ебушки-воробушки? А вот зачем это надо, слушай сюда:

  1. Связанность уменьшить, чтоб её. Чтобы пакет, который делает работу, нихуя не знал о тех, кто его использует. Ну представь, блядь, ты пишешь библиотеку, а от тебя требуют: «А объяви-ка ты интерфейс для того мудака, который тебя будет вызывать». Это ж пиздец, блядь! Циклические зависимости попрут, как тараканы. А так — потребитель сам говорит: «Мне вот такой контракт нужен», а реализатор молча его выполняет. Красота, ёпта!

  2. Принцип «Не суй мне всё в один интерфейс». Потребитель — он жадный, но умный. Он не будет требовать от тебя 25 методов, если ему надо всего два. Он и определит интерфейс ровно из этих двух методов. Получаются интерфейсы маленькие, аккуратные, а не этакая манда с ушами на 100500 методов, которую заебёшься реализовывать.

  3. Тестирование, блядь, упрощается в разы. Ну вот смотри: интерфейс у тебя в пакете-потребителе. Тебе надо написать тест. И что ты делаешь? Правильно, пишешь заглушку (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 сам увидит, что методы-то подходят. А если завтра надо будет брать данные не из базы, а из файла или с потолка — ты просто создашь новый тип с таким же методом, и потребитель даже не заметит подмены. Гибко, модульно, и не ебёт мозг. Вот так-то, блядь!