Что такое интерфейсы
Интерфейсы в Go — это механизм абстракции, который определяет набор методов, но не реализует их. Тип считается реализующим интерфейс, если он имеет все методы, объявленные в интерфейсе, с совпадающими сигнатурами. Это позволяет писать гибкий код, работающий с разными типами через общее поведение, без привязки к конкретным реализациям.
Как это работает
Интерфейс объявляется с помощью ключевого слова type и содержит сигнатуры методов. Например, стандартный интерфейс io.Reader:
type Reader interface {
Read(p []byte) (n int, err error)
}Любой тип, у которого есть метод Read с такой же сигнатурой, автоматически удовлетворяет этому интерфейсу. В Go реализация интерфейса неявная: не нужно явно указывать, что тип реализует интерфейс, как, например, в Java или C#. Достаточно просто определить методы.
Пример
Рассмотрим интерфейс Shape с методом Area, который возвращает площадь фигуры. Реализуем его для структур Circle и Square:
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
type Square struct {
Side float64
}
func (s Square) Area() float64 {
return s.Side * s.Side
}
func printArea(shape Shape) {
fmt.Println(shape.Area())
}Теперь функция printArea принимает любой тип, реализующий Shape, и вызывает его метод Area. Это и есть полиморфизм: один и тот же код работает с разными типами.
Подводные камни
- Указатели и значения: если метод объявлен на указателе (
func (c *Circle) Area()), то интерфейсу удовлетворяет только указатель наCircle, а не значение. Это частая ошибка. - Пустой интерфейс:
interface{}(илиanyв Go 1.18+) не имеет методов, поэтому ему удовлетворяет любой тип. Его используют для работы с произвольными значениями, но злоупотребление приводит к потере типобезопасности. - Интерфейсы как контракты: интерфейс должен описывать поведение, а не данные. Если интерфейс слишком большой, его сложно реализовать; лучше дробить на маленькие интерфейсы.
- Производительность: вызов метода через интерфейс немного медленнее, чем прямой вызов, из-за динамической диспетчеризации. В горячих путях это может быть критично.
Когда использовать
Интерфейсы применяют для:
- абстракции зависимостей (например, репозиторий в приложении);
- написания функций, которые принимают разные реализации (например,
io.Reader); - мокирования в тестах: интерфейс позволяет подменить реальную реализацию заглушкой.
Коротко
- Интерфейс — это набор методов, реализация которых не задана.
- Реализация неявная: тип удовлетворяет интерфейсу, если у него есть все нужные методы.
- Интерфейсы дают полиморфизм и гибкость, но требуют аккуратности с указателями и размером интерфейса.
- Пустой интерфейс
interface{}принимает любой тип, но злоупотреблять им не стоит.
