Ответ
Это одна из фундаментальных идиом в 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, которые легко использовать и расширять, при этом сохраняя низкую связанность между компонентами системы.