Требовать поведение, а не конкретную структуру
Функции иногда нужно уметь выполнить одно действие с разными типами данных. Вместо отдельной функции для каждого типа можно описать нужный набор методов интерфейсом. Любой тип автоматически удовлетворяет интерфейсу, если его набор методов содержит все методы с подходящими именами и сигнатурами.
Интерфейс полезнее всего определять рядом с кодом, который его принимает, и оставлять небольшим: потребителю следует требовать только нужное ему действие. В параметрах используют сам интерфейс, например io.Reader, а не указатель на интерфейс. Передавать указатель на конкретное значение можно, если это нужно реализации.
Пример
package main
import "fmt"
type measurer interface {
area() float64
}
type rectangle struct {
width float64
height float64
}
func (r rectangle) area() float64 {
return r.width * r.height
}
type circle struct {
radius float64
}
func (c circle) area() float64 {
return 3.14 * c.radius * c.radius
}
func printArea(name string, shape measurer) {
fmt.Printf("%s: %.2f\n", name, shape.area())
}
func main() {
printArea("прямоугольник", rectangle{width: 4, height: 2})
printArea("круг", circle{radius: 1})
}
Функция printArea знает только метод area, поэтому обе формы подходят без регистрации или объявления наследования. Оба метода имеют получатель-значение, значит и значение, и указатель на соответствующую структуру имеют этот метод в своём наборе методов. Программа печатает «прямоугольник: 8.00» и «круг: 3.14».
Если метод объявлен с получателем-указателем, например func (r *rectangle) resize(...), интерфейс с этим методом реализует только *rectangle, но не rectangle. Вызов метода на адресуемом значении иногда автоматически берёт его адрес, однако это правило вызова не меняет набор методов типа. Поэтому интерфейсное значение в таком случае заполняют указателем: var item resizer = &shape.
Небольшой интерфейс не означает, что нужно создавать интерфейс для каждого типа. Он нужен на границе, где потребителю действительно требуется несколько возможных реализаций.