🐹 Go
Structs e Interfaces em Go
Структуры и интерфейсы в Go: ООП без классов
В Go нет классов — и это сделано намеренно! Вместо них используются структуры (struct) и интерфейсы (interface). Это другой подход к объектно-ориентированному программированию — более простой и гибкий. Давай разберёмся!
Структуры: данные без классов
package main
import "fmt"
// Объявление структуры
type Person struct {
Name string
Age int
City string
}
type Dog struct {
Name string
Breed string
Age int
}
func main() {
// Создание экземпляра структуры
alice := Person{
Name: "Алиса",
Age: 25,
City: "Москва",
}
// Сокращённая форма (порядок важен)
bob := Person{"Борис", 30, "СПб"}
// Через var (zero values)
var vera Person
vera.Name = "Вера"
vera.Age = 22
// Доступ к полям
fmt.Println(alice.Name) // Алиса
fmt.Println(bob.Age) // 30
// Изменение полей
alice.City = "Новосибирск"
fmt.Println(alice)
// Указатель на структуру
p := &alice
p.Age = 26 // Go автоматически разыменовывает (*p).Age
fmt.Println(alice.Age) // 26 — изменилось через указатель!
}
Методы: функции привязанные к структуре
package main
import (
"fmt"
"math"
)
type Circle struct {
X, Y float64 // Центр
Radius float64
}
type Rectangle struct {
Width, Height float64
}
// Метод — функция с "получателем" (receiver)
// (c Circle) — значение-получатель (копия)
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (c Circle) Perimeter() float64 {
return 2 * math.Pi * c.Radius
}
func (c Circle) String() string {
return fmt.Sprintf("Circle(r=%.2f)", c.Radius)
}
// Методы Rectangle
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func (r Rectangle) Perimeter() float64 {
return 2 * (r.Width + r.Height)
}
// Указатель-получатель — изменяет оригинал
func (c *Circle) Scale(factor float64) {
c.Radius *= factor // Изменяем оригинал через указатель
}
func main() {
c := Circle{X: 0, Y: 0, Radius: 5}
r := Rectangle{Width: 4, Height: 6}
fmt.Printf("Круг: %s\n", c) // Circle(r=5.00)
fmt.Printf("Площадь: %.2f\n", c.Area()) // 78.54
fmt.Printf("Периметр: %.2f\n", c.Perimeter()) // 31.42
fmt.Printf("Прямоугольник: %.0fx%.0f\n", r.Width, r.Height) // 4x6
fmt.Printf("Площадь: %.2f\n", r.Area()) // 24.00
// Метод с указателем изменяет оригинал
c.Scale(2) // Радиус становится 10
fmt.Printf("После Scale(2): %s\n", c) // Circle(r=10.00)
}
Интерфейсы: утиная типизация
«Утиная типизация» — если что-то ходит как утка и крякает как утка, то это утка». В Go: если тип реализует все методы интерфейса — он автоматически реализует интерфейс. Не нужно явно писать «implements»!
package main
import (
"fmt"
"math"
)
// Интерфейс Shape
type Shape interface {
Area() float64
Perimeter() float64
}
// Структуры
type Circle2 struct{ Radius float64 }
type Rectangle2 struct{ Width, Height float64 }
type Triangle struct{ A, B, C float64 }
// Реализация Shape для Circle2
func (c Circle2) Area() float64 { return math.Pi * c.Radius * c.Radius }
func (c Circle2) Perimeter() float64 { return 2 * math.Pi * c.Radius }
// Реализация Shape для Rectangle2
func (r Rectangle2) Area() float64 { return r.Width * r.Height }
func (r Rectangle2) Perimeter() float64 { return 2 * (r.Width + r.Height) }
// Реализация Shape для Triangle (формула Герона)
func (t Triangle) Area() float64 {
s := (t.A + t.B + t.C) / 2
return math.Sqrt(s * (s - t.A) * (s - t.B) * (s - t.C))
}
func (t Triangle) Perimeter() float64 { return t.A + t.B + t.C }
// Функция принимает ЛЮБОЙ Shape
func printShapeInfo(s Shape) {
fmt.Printf("Площадь: %.2f, Периметр: %.2f\n", s.Area(), s.Perimeter())
}
func totalArea(shapes []Shape) float64 {
total := 0.0
for _, s := range shapes {
total += s.Area()
}
return total
}
func main() {
shapes := []Shape{
Circle2{Radius: 5},
Rectangle2{Width: 4, Height: 6},
Triangle{A: 3, B: 4, C: 5},
}
for _, s := range shapes {
printShapeInfo(s)
}
fmt.Printf("Суммарная площадь: %.2f\n", totalArea(shapes))
}
Пустой интерфейс: any
package main
import "fmt"
// interface{} (или any в Go 1.18+) принимает ЛЮБОЙ тип
func printAnything(value interface{}) {
fmt.Printf("Тип: %T, Значение: %v\n", value, value)
}
func describe(i interface{}) {
// Type assertion — проверка и извлечение типа
switch v := i.(type) {
case int:
fmt.Printf("Целое: %d\n", v)
case string:
fmt.Printf("Строка: %s\n", v)
case bool:
fmt.Printf("Булево: %t\n", v)
case []int:
fmt.Printf("Срез int длиной %d\n", len(v))
default:
fmt.Printf("Неизвестный тип: %T\n", v)
}
}
func main() {
printAnything(42)
printAnything("привет")
printAnything(3.14)
printAnything(true)
describe(42)
describe("Go")
describe([]int{1, 2, 3})
// any — алиас для interface{} (Go 1.18+)
var x any = "строка"
x = 42
x = []string{"a", "b"}
fmt.Println(x)
}
Embedding: встраивание структур
package main
import "fmt"
type Animal struct {
Name string
Age int
}
func (a Animal) Describe() {
fmt.Printf("%s, %d лет\n", a.Name, a.Age)
}
func (a Animal) Sleep() {
fmt.Printf("%s спит\n", a.Name)
}
// Dog встраивает Animal (embedding)
type DogE struct {
Animal // Встроенная структура
Breed string
}
func (d DogE) Bark() {
fmt.Printf("%s: Гав!\n", d.Name) // Доступ к полям Animal!
}
// Cat тоже встраивает Animal
type CatE struct {
Animal
Indoor bool
}
func (c CatE) Meow() {
fmt.Printf("%s: Мяу!\n", c.Name)
}
// Переопределение метода
func (c CatE) Describe() {
fmt.Printf("Кот %s, %d лет", c.Name, c.Age)
if c.Indoor {
fmt.Print(" (домашний)")
}
fmt.Println()
}
func main() {
dog := DogE{
Animal: Animal{Name: "Рекс", Age: 3},
Breed: "Овчарка",
}
// Прямой доступ к полям Animal
fmt.Println(dog.Name) // Рекс (не dog.Animal.Name!)
dog.Describe() // Рекс, 3 лет
dog.Sleep() // Рекс спит
dog.Bark() // Рекс: Гав!
cat := CatE{
Animal: Animal{Name: "Мурка", Age: 5},
Indoor: true,
}
cat.Meow() // Мурка: Мяу!
cat.Describe() // Кот Мурка, 5 лет (домашний)
cat.Sleep() // Мурка спит (унаследован от Animal)
}
Итого
- ✅ struct — структура данных, аналог класса без наследования
- ✅ Методы — функции с получателем
(t Type)или(t *Type) - ✅ Указатель-получатель
*Type— изменяет оригинал, значениеType— работает с копией - ✅ interface — набор сигнатур методов; тип реализует интерфейс автоматически
- ✅ Утиная типизация — «если ходит как утка» — явного «implements» нет
- ✅ interface{} / any — принимает любой тип
- ✅ Embedding — встраивание структур для переиспользования кода
Методы на указателях vs значениях
package main
import "fmt"
type Temperature struct {
Celsius float64
}
// Получатель-значение — работает с копией
func (t Temperature) Fahrenheit() float64 {
return t.Celsius*9/5 + 32
}
// Получатель-указатель — изменяет оригинал
func (t *Temperature) SetCelsius(c float64) {
t.Celsius = c
}
// Правило: если метод изменяет состояние — используй указатель
// Если только читает — можно значение (но для единообразия лучше тоже указатель)
func main() {
temp := Temperature{Celsius: 100}
fmt.Printf("%.1f°C = %.1f°F\n", temp.Celsius, temp.Fahrenheit())
// 100.0°C = 212.0°F
temp.SetCelsius(37)
fmt.Printf("Температура тела: %.1f°C = %.1f°F\n", temp.Celsius, temp.Fahrenheit())
// 37.0°C = 98.6°F
}
Сортировка с интерфейсом sort.Interface
package main
import (
"fmt"
"sort"
)
type Student struct {
Name string
Grade int
}
type ByGrade []Student
func (b ByGrade) Len() int { return len(b) }
func (b ByGrade) Less(i, j int) bool { return b[i].Grade > b[j].Grade } // По убыванию
func (b ByGrade) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
func main() {
students := []Student{
{"Алиса", 95},
{"Борис", 72},
{"Вера", 88},
{"Гриша", 91},
}
sort.Sort(ByGrade(students))
fmt.Println("Рейтинг студентов:")
for i, s := range students {
fmt.Printf("%d. %s: %d\n", i+1, s.Name, s.Grade)
}
// Современный способ (Go 1.18+)
sort.Slice(students, func(i, j int) bool {
return students[i].Name < students[j].Name // По алфавиту
})
fmt.Println("\nПо алфавиту:")
for _, s := range students {
fmt.Printf(" %s: %d\n", s.Name, s.Grade)
}
}
Встроенные интерфейсы стандартной библиотеки
package main
import (
"fmt"
"strings"
)
// Stringer — интерфейс для вывода
type Point struct{ X, Y float64 }
func (p Point) String() string {
return fmt.Sprintf("(%.1f, %.1f)", p.X, p.Y)
}
// io.Writer — запись в поток
type Logger struct {
prefix string
buf strings.Builder
}
func (l *Logger) Write(p []byte) (n int, err error) {
l.buf.WriteString(l.prefix)
l.buf.Write(p)
return len(p), nil
}
func (l *Logger) String() string { return l.buf.String() }
func main() {
p := Point{3.5, 4.7}
fmt.Println(p) // (3.5, 4.7) — вызывает String() автоматически!
log := &Logger{prefix: "[INFO] "}
fmt.Fprintf(log, "Сервер запущен на порту %d\n", 8080)
fmt.Print(log.String()) // [INFO] Сервер запущен на порту 8080
}
Итого
- ✅ struct — структура данных, аналог класса без наследования
- ✅ Методы:
func (r ReceiverType) MethodName() - ✅ Получатель-указатель
*T— изменяет оригинал - ✅ Получатель-значение
T— работает с копией - ✅ interface — набор сигнатур методов, реализуется неявно
- ✅ Утиная типизация — если метод есть — интерфейс реализован
- ✅ interface{} / any — принимает любой тип
- ✅ Embedding — встраивание для переиспользования кода
- ✅ type assertion:
v.(Type)илиv, ok := x.(Type)
Работа с JSON
package main
import (
"encoding/json"
"fmt"
)
// Теги для JSON сериализации
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // omitempty — пропустить если пусто
Password string `json:"-"` // - — всегда пропускать
Age int `json:"age"`
}
func main() {
// Структура → JSON (Marshal)
user := User{ID: 1, Name: "Алиса", Email: "alice@example.com", Password: "secret123", Age: 25}
data, err := json.MarshalIndent(user, "", " ")
if err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Println(string(data))
// {
// "id": 1,
// "name": "Алиса",
// "email": "alice@example.com",
// "age": 25
// }
// Password НЕТ — помечен как json:"-"
// JSON → Структура (Unmarshal)
jsonStr := `{"id":2,"name":"Борис","age":30}`
var user2 User
if err := json.Unmarshal([]byte(jsonStr), &user2); err != nil {
fmt.Println("Ошибка:", err)
return
}
fmt.Printf("Пользователь: %s, %d лет\n", user2.Name, user2.Age)
// Массив структур
users := []User{
{ID: 1, Name: "Алиса", Age: 25},
{ID: 2, Name: "Борис", Age: 30},
}
data2, _ := json.Marshal(users)
fmt.Println(string(data2))
}
Практика: стек и очередь
package main
import "fmt"
// Стек на основе среза
type Stack[T any] struct {
items []T
}
func (s *Stack[T]) Push(item T) { s.items = append(s.items, item) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 { return zero, false }
item := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return item, true
}
func (s *Stack[T]) Peek() (T, bool) {
var zero T
if len(s.items) == 0 { return zero, false }
return s.items[len(s.items)-1], true
}
func (s *Stack[T]) Size() int { return len(s.items) }
func main() {
stack := &Stack[int]{}
stack.Push(1); stack.Push(2); stack.Push(3)
fmt.Println("Стек (LIFO):")
for stack.Size() > 0 {
if val, ok := stack.Pop(); ok {
fmt.Println(val) // 3, 2, 1
}
}
}
Паттерн Option: цепочка методов
package main
import "fmt"
// Functional options — гибкая конфигурация
type Server struct {
host string
port int
timeout int
debug bool
}
type Option func(*Server)
func WithHost(host string) Option {
return func(s *Server) { s.host = host }
}
func WithPort(port int) Option {
return func(s *Server) { s.port = port }
}
func WithDebug(debug bool) Option {
return func(s *Server) { s.debug = debug }
}
func NewServer(opts ...Option) *Server {
s := &Server{host: "localhost", port: 8080, timeout: 30}
for _, opt := range opts {
opt(s)
}
return s
}
func (s *Server) Info() string {
return fmt.Sprintf("Server{%s:%d, debug:%v, timeout:%ds}",
s.host, s.port, s.debug, s.timeout)
}
func main() {
// Минимальная конфигурация
s1 := NewServer()
fmt.Println(s1.Info())
// Полная конфигурация
s2 := NewServer(
WithHost("0.0.0.0"),
WithPort(9090),
WithDebug(true),
)
fmt.Println(s2.Info())
}
Полезные паттерны работы со структурами
package main
import "fmt"
// Конструктор (по соглашению New...)
type Point struct{ X, Y float64 }
func NewPoint(x, y float64) *Point { return &Point{x, y} }
func (p *Point) Distance(other *Point) float64 {
dx, dy := p.X-other.X, p.Y-other.Y
return math.Sqrt(dx*dx + dy*dy)
}
func (p *Point) String() string { return fmt.Sprintf("(%.1f,%.1f)", p.X, p.Y) }
func main() {
p1 := NewPoint(0, 0)
p2 := NewPoint(3, 4)
fmt.Println(p1, p2) // (0.0,0.0) (3.0,4.0)
}