Интерфейсы: основы
Что такое интерфейсы в Go?
Интерфейс в Go — это тип, который определяет набор методов. Если какой-то тип реализует все методы интерфейса, то он автоматически удовлетворяет этому интерфейсу. Это называется утиной типизацией (duck typing): "Если что-то ходит как утка и крякает как утка, то это утка".
Интерфейсы позволяют писать более гибкий и переиспользуемый код, абстрагируясь от конкретных реализаций.
Объявление интерфейса
Интерфейс объявляется с помощью ключевого слова interface. Давайте создадим простой интерфейс для геометрических фигур:
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:
// Функция принимает любой тип, реализующий интерфейс 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 автоматически проверяет, реализует ли тип интерфейс. Если тип не реализует все методы интерфейса, возникнет ошибка компиляции:
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()
}Интерфейсы как параметры
Одно из главных преимуществ интерфейсов — возможность писать функции, которые работают с абстракциями, а не с конкретными типами:
// Утилитарная функция для сравнения фигур
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:
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())
}