Встречается на 75% собеседований по Go

Что такое интерфейсы

Интерфейсы в Go — это механизм абстракции, который определяет набор методов, но не реализует их. Тип считается реализующим интерфейс, если он имеет все методы, объявленные в интерфейсе, с совпадающими сигнатурами. Это позволяет писать гибкий код, работающий с разными типами через общее поведение, без привязки к конкретным реализациям.

Как это работает

Интерфейс объявляется с помощью ключевого слова type и содержит сигнатуры методов. Например, стандартный интерфейс io.Reader:

go
type Reader interface {
    Read(p []byte) (n int, err error)
}

Любой тип, у которого есть метод Read с такой же сигнатурой, автоматически удовлетворяет этому интерфейсу. В Go реализация интерфейса неявная: не нужно явно указывать, что тип реализует интерфейс, как, например, в Java или C#. Достаточно просто определить методы.

Пример

Рассмотрим интерфейс Shape с методом Area, который возвращает площадь фигуры. Реализуем его для структур Circle и Square:

go
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{} принимает любой тип, но злоупотреблять им не стоит.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы