Это одна из самых важных глав. В Go нет классов в привычном понимании (как в Java или Python), но есть структуры и интерфейсы, которые позволяют строить сложные системы гораздо эффективнее.
Использование только базовых типов в больших программах быстро становится утомительным. Если мы описываем геометрические фигуры, нам приходится передавать множество координат отдельными аргументами:
func circleArea(x, y, r float64) float64 {
return math.Pi * r * r
}
Такой подход провоцирует ошибки: легко перепутать x с y или радиус с координатой. Нам нужен способ объединить связанные данные в одну сущность.
Структуры (Structs)
Структура — это тип данных, который объединяет в себе несколько полей под одним именем.
type Circle struct {
x, y, r float64
}
Здесь type Circle создает новый тип, а struct описывает его внутреннее устройство.
Инициализация структур
Создать экземпляр структуры можно несколькими способами:
- Нулевое значение:
var c Circle(все поля будут0.0). - Функция new:
c := new(Circle)(вернет указатель*Circle, поля равны0.0). - Литерал (Рекомендуемый способ): ```go c := Circle{x: 0, y: 0, r: 5} // Именованные поля (безопасно) c2 := Circle{0, 0, 5} // По порядку (не рекомендуется для больших структур)
### Доступ к полям
Доступ осуществляется через точку: `fmt.Println(c.r)`. Это работает как для самих структур, так и для указателей на них (Go автоматически разыменует указатель).
---
## Методы (Methods)
Метод — это функция, привязанная к конкретному типу. Между ключевым словом `func` и названием метода добавляется **получатель (receiver)**.
```go
// (c *Circle) — это получатель
func (c *Circle) Area() float64 {
return math.Pi * c.r * c.r
}
Теперь мы можем вызывать функцию прямо у объекта: c.Area().
[!TIP] Указатель или Значение? Если вы планируете изменять поля структуры внутри метода или если структура большая, всегда используйте указатель в получателе
(c *Circle). Это предотвратит лишнее копирование данных в памяти.
Встраивание типов (Composition)
В Go нет наследования классов. Вместо него используется композиция через встраивание анонимных полей.
type Person struct {
Name string
}
func (p *Person) Talk() {
fmt.Println("Привет, я", p.Name)
}
type Android struct {
Person // Встроенное поле (анонимное)
Model string
}
Теперь Android «наследует» методы Person. Мы можем вызвать android.Talk() напрямую, хотя метод определен для Person.
Интерфейсы (Interfaces)
Интерфейс определяет поведение. Это набор сигнатур методов, которые должен иметь тип, чтобы «реализовать» этот интерфейс.
В Go интерфейсы реализуются неявно. Вам не нужно писать implements Shape, достаточно просто иметь нужные методы.
type Shape interface {
Area() float64
}
Любой тип, у которого есть метод Area() float64, автоматически считается фигурой (Shape).
Использование интерфейсов
Интерфейсы позволяют писать универсальный код. Например, функцию, которая считает общую площадь любых фигур:
func TotalArea(shapes ...Shape) float64 {
var total float64
for _, s := range shapes {
total += s.Area()
}
return total
}
Пустой интерфейс any
Начиная с Go 1.18, ключевое слово any является псевдонимом для interface{}. Переменная типа any может хранить значение любого типа.
Задачи
- Теория: В чем фундаментальная разница между методом и обычной функцией?
- Периметр: Добавьте метод
Perimeter()(периметр) для структурCircleиRectangle. - Для круга:
-
Для прямоугольника:
- Интерфейс: Добавьте
Perimeter()в интерфейсShape. Обновите функциюTotalArea, чтобы она также могла выводить общий периметр всех фигур. - Встраивание: Создайте структуру
SmartPhone, которая встраивает в себяAndroid. Попробуйте вызвать методTalk()от лица смартфона.
Полезные ссылки
- Go by Example: Structs и Methods.
- Effective Go: Interfaces and other types.
- Go by Example: Interfaces.
- Generics in Go — если хотите заглянуть в будущее и узнать, как делать типы еще более универсальными.