Назад к урокам

Интерфейсы в Go

Урок объясняет, что такое интерфейсы в Go, как они позволяют описывать поведение вместо типов, как работает неявная реализация, чем отличаются value и pointer receivers, и как использовать type assertion для получения реального типа.

Начинающий12 min

🧩 Интерфейсы в Go

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


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

Интерфейс определяет набор методов, которые должны быть у типа, чтобы он считался реализацией этого интерфейса.

Пример:

type Shape interface {
    Area() float64
}

Интерфейс Shape говорит: «любой тип, у которого есть метод Area() float64, является фигурой».


🛠 Реализация интерфейса (неявная)

В Go не нужно явно указывать, что тип реализует интерфейс.

type Rectangle struct {
    Width, Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

Теперь Rectangle автоматически считается реализацией Shape.


▶️ Использование интерфейса

Функция может принимать любой тип, который реализует интерфейс:

func PrintArea(s Shape) {
    fmt.Println(s.Area())
}

Использование:

rect := Rectangle{10, 5}
PrintArea(rect)

📌 Value receiver и Pointer receiver

Тип может реализовать интерфейс через: - value receiver --- работает с копией структуры; - pointer receiver --- работает с указателем, может изменять данные.

Реализация через значение:

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

Можно передавать: - значение (rect), - указатель (&rect).

Реализация через указатель:

func (r *Rectangle) Scale(f float64) {
    r.Width *= f
    r.Height *= f
}

Тогда метод доступен только через указатель.


⚠️ Важные правила работы с интерфейсами

  • Интерфейсы в Go работают по принципу duck typing: > Если что-то умеет "квакать" как утка --- это утка.
  • Реализация происходит автоматически на основе методов.
  • Не используйте указатели на интерфейсы (*MyInterface) --- они бессмысленны.
  • Лучше создавать несколько маленьких интерфейсов, чем один огромный.

🔍 Type assertion (извлечение конкретного типа)

Иногда нужно преобразовать интерфейс обратно в конкретный тип:

var s Shape = Rectangle{10, 5}

rect, ok := s.(Rectangle)
if ok {
    fmt.Println("Width:", rect.Width)
}

🔄 Type switch

Используется, чтобы выполнять код в зависимости от конкретного типа, скрытого за интерфейсом:

func Describe(x interface{}) {
    switch v := x.(type) {
    case int:
        fmt.Println("int:", v)
    case string:
        fmt.Println("string:", v)
    default:
        fmt.Println("unknown")
    }
}

🧩 Пример: общий интерфейс для ресета объектов

type Resetter interface {
    Reset()
}

type Player struct {
    Health int
}

func (p *Player) Reset() {
    p.Health = 100
}

Использование:

p := &Player{Health: 30}
p.Reset()        // здоровье сброшено

📘 Итоги

  • Интерфейсы описывают набор методов, а не структуру данных.
  • Реализация интерфейса происходит неявно.
  • Методы на указателях и значениях ведут себя по-разному.
  • type assertion и type switch позволяют работать с реальными типами.
  • Интерфейсы делают код гибким, расширяемым и чистым.
Интерфейсы в Go | WebSchool