Модуль
1Интерфейсы: основы← вы здесь2Стандартные интерфейсы: io.Reader, Stringer, error3Пустой интерфейс и any4Type assertion и type switch
Урок 1~0 минут

Интерфейсы: основы

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

Интерфейс в Go — это тип, который определяет набор методов. Если какой-то тип реализует все методы интерфейса, то он автоматически удовлетворяет этому интерфейсу. Это называется утиной типизацией (duck typing): "Если что-то ходит как утка и крякает как утка, то это утка".

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


Объявление интерфейса

Интерфейс объявляется с помощью ключевого слова interface. Давайте создадим простой интерфейс для геометрических фигур:

go
package main
 
import (
    "fmt"
    "math"
)
 
// Объявляем интерфейс Shape
type Shape interface {
    Area() float64
    Perimeter() float64
}
 
// Структура Circle реализует интерфейс Shape
type Circle struct {
    Radius float64
}
 
// Метод Area для Circle
func (c Circle) Area() float64 {
    return math.Pi * c.Radius * c.Radius
}
 
// Метод Perimeter для Circle
func (c Circle) Perimeter() float64 {
    return 2 * math.Pi * c.Radius
}
 
// Структура Rectangle тоже реализует интерфейс Shape
type Rectangle struct {
    Width, Height float64
}
 
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}
 
func (r Rectangle) Perimeter() float64 {
    return 2 * (r.Width + r.Height)
}
 
func main() {
    var s Shape // Переменная типа интерфейс Shape
    
    s = Circle{Radius: 5}
    fmt.Printf("Круг: площадь = %.2f, периметр = %.2f\n", 
        s.Area(), s.Perimeter())
    
    s = Rectangle{Width: 3, Height: 4}
    fmt.Printf("Прямоугольник: площадь = %.2f, периметр = %.2f\n", 
        s.Area(), s.Perimeter())
}

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

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

go
// Функция принимает любой тип, реализующий интерфейс Shape
func PrintShapeInfo(s Shape) {
    fmt.Printf("Площадь: %.2f, Периметр: %.2f\n", 
        s.Area(), s.Perimeter())
}
 
func main() {
    circle := Circle{Radius: 7}
    rectangle := Rectangle{Width: 6, Height: 8}
    
    // Одна функция работает с разными типами
    PrintShapeInfo(circle)
    PrintShapeInfo(rectangle)
    
    // Можно использовать интерфейс в слайсах
    shapes := []Shape{
        Circle{Radius: 3},
        Rectangle{Width: 4, Height: 5},
        Circle{Radius: 2.5},
    }
    
    fmt.Println("\nВсе фигуры в слайсе:")
    for _, shape := range shapes {
        PrintShapeInfo(shape)
    }
}

Проверка реализации интерфейса

Компилятор Go автоматически проверяет, реализует ли тип интерфейс. Если тип не реализует все методы интерфейса, возникнет ошибка компиляции:

go
type Triangle struct {
    A, B, C float64
}
 
// Triangle реализует только Area(), но не Perimeter()
func (t Triangle) Area() float64 {
    // Формула Герона
    p := (t.A + t.B + t.C) / 2
    return math.Sqrt(p * (p - t.A) * (p - t.B) * (p - t.C))
}
 
func main() {
    var s Shape
    // Эта строка вызовет ошибку компиляции:
    // Triangle does not implement Shape (missing Perimeter method)
    // s = Triangle{A: 3, B: 4, C: 5}
    
    // Чтобы исправить, нужно добавить метод Perimeter()
}

Интерфейсы как параметры

Одно из главных преимуществ интерфейсов — возможность писать функции, которые работают с абстракциями, а не с конкретными типами:

go
// Утилитарная функция для сравнения фигур
func CompareAreas(s1, s2 Shape) {
    area1 := s1.Area()
    area2 := s2.Area()
    
    switch {
    case area1 > area2:
        fmt.Printf("Первая фигура больше на %.2f\n", area1-area2)
    case area2 > area1:
        fmt.Printf("Вторая фигура больше на %.2f\n", area2-area1)
    default:
        fmt.Println("Фигуры равны по площади")
    }
}
 
func main() {
    circle := Circle{Radius: 5}
    rect := Rectangle{Width: 8, Height: 6}
    
    // Сравниваем совершенно разные типы через общий интерфейс
    CompareAreas(circle, rect)
}

Практический пример: Writer интерфейс

В стандартной библиотеке Go много встроенных интерфейсов. Один из самых популярных — io.Writer:

go
package main
 
import (
    "fmt"
    "os"
)
 
// Функция, которая работает с любым Writer
func WriteMessage(writer interface {
    Write([]byte) (int, error)
}, message string) {
    writer.Write([]byte(message))
}
 
func main() {
    // Используем стандартный вывод (реализует Writer)
    WriteMessage(os.Stdout, "Привет, мир!\n")
    
    // Файл тоже реализует Writer
    file, err := os.Create("message.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    
    WriteMessage(file, "Сообщение в файл\n")
    
    // bytes.Buffer тоже реализует Writer
    var buf bytes.Buffer
    WriteMessage(&buf, "Сообщение в буфер\n")
    fmt.Println("Буфер содержит:", buf.String())
}
ИНТЕРФЕЙС
type Shape interface {
    Area()      float64
    Perimeter() float64
}
CIRCLE (реализация)
type Circle struct { R float64 }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }
func (c Circle) Perimeter() float64 { return 2 * math.Pi * c.R }
ПРОВЕРКА КОНТРАКТА
Area() float64
Perimeter() float64
RECT (реализация)
type Rect struct { W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func (r Rect) Perimeter() float64 { return 2 * (r.W + r.H) }
ПРОВЕРКА КОНТРАКТА
Area() float64
Perimeter() float64
ЖИВОЙ РАСЧЁТ
Circle
Area():
78.54
Perimeter():
31.42
Rectangle
Area():
12.00
Perimeter():
14.00
Утиная типизация: никакого ключевого слова implements не нужно. Если тип имеет все методы интерфейса — он автоматически его реализует. Go проверяет это во время компиляции.