Ответ
Это одна из фундаментальных идиом в Go, которая помогает писать гибкий, слабосвязанный и понятный код. Она состоит из двух частей:
1. Принимай интерфейсы (Accept Interfaces)
Функции и методы должны принимать в качестве аргументов интерфейсы, а не конкретные типы.
- Почему? Это делает функцию более универсальной и тестируемой. Она зависит от поведения (методов интерфейса), а не от конкретной реализации. Вы можете передать в неё любой тип, который удовлетворяет этому интерфейсу, включая моки для тестов.
// Функция принимает любой тип, реализующий io.Reader
func countLines(r io.Reader) (int, error) {
scanner := bufio.NewScanner(r)
count := 0
for scanner.Scan() {
count++
}
return count, scanner.Err()
}
// Мы можем передать как файл, так и строку:
file, _ := os.Open("text.txt")
countLines(file)
strReader := strings.NewReader("line1nline2")
countLines(strReader)
2. Возвращай структуры (Return Structs)
Функции, которые создают и возвращают новые значения, должны возвращать конкретные типы (чаще всего указатели на структуры), а не интерфейсы.
- Почему? Это даёт потребителю (вызывающему коду) максимальную гибкость. Возвращая конкретный тип, вы предоставляете всю его функциональность (все поля и методы). Потребитель сам решит, нужно ли ему использовать это значение через какой-либо интерфейс или работать с ним напрямую. Если бы вы вернули интерфейс, потребитель был бы ограничен только методами этого интерфейса.
// Фабричная функция возвращает конкретный тип *User
type User struct {
ID int
Name string
}
func NewUser(name string) *User {
return &User{Name: name}
}
// Потребитель получает *User и может делать с ним что угодно
user := NewUser("Alice")
fmt.Println(user.ID, user.Name)
Итог: Этот принцип приводит к созданию API, которые легко использовать и расширять, при этом сохраняя низкую связанность между компонентами системы.
Ответ 18+ 🔞
А, слушай, вот это, блядь, принцип, который в Go как мантра, ёпта! Без него код превращается в такое монструозное говно, что потом сам себя не разберёшь. Суть проще, чем кажется, но, блядь, важнее некуда.
1. Принимай интерфейсы, блядь (Accept Interfaces)
Вот смотри, когда пишешь функцию — не затачивай её под какую-то конкретную хрень. Пусть она ждёт не *os.File или *bytes.Buffer, а интерфейс. То есть, она говорит: «Да мне похуй, что ты мне передашь, лишь бы у тебя был метод Read()!».
- Зачем это, ёбта? Да чтобы не привязываться намертво! Функция становится универсальной, как швейцарский нож, и её проще тестировать. Подсунул ей в тестах какую-нибудь заглушку (mock), и всё, пиздец, работает.
// Эта функция схавает ВСЁ, что умеет читать (io.Reader)
func countLines(r io.Reader) (int, error) {
scanner := bufio.NewScanner(r)
count := 0
for scanner.Scan() {
count++
}
return count, scanner.Err()
}
// И похуй, откуда данные — из файла или из строки:
file, _ := os.Open("text.txt")
countLines(file) // Работает!
strReader := strings.NewReader("line1nline2")
countLines(strReader) // И это работает! Удивление пиздец!
2. Возвращай структуры, ёпта (Return Structs)
А вот когда функция что-то создаёт и отдаёт — тут уже не надо выёбываться. Отдавай конкретную структуру, а не интерфейс.
- А это нахуя? Да чтобы не ограничивать того, кто эту хрень использует! Вернул структуру — и у пользователя полный доступ ко всем её полям и методам. Он сам решит, работать с ней напрямую или через интерфейс. А если вернёшь интерфейс — то ты, сука, сам же и загонишь его в рамки, оставив только методы из интерфейса. Зачем так делать-то, а?
// Фабрика должна отдавать конкретную структуру *User, а не какую-то абстракцию
type User struct {
ID int
Name string
}
func NewUser(name string) *User {
return &User{Name: name}
}
// Получатель получает ВСЮ власть над объектом
user := NewUser("Alice")
fmt.Println(user.ID, user.Name) // Может всё, что душе угодно
Короче, суть в чём: Пиши так, чтобы твой код был как конструктор «Лего» — все детали универсальные, соединяются легко, и можно собрать что угодно, не ломая мозг. А не как пазл, где одна кривая деталь нахуй всю картину к ебеням. Вот и весь принцип, в рот меня чих-пых!