Методи¶
Метод — це функція з отримувачем (receiver) — типізованим параметром, який
з'являється між func і назвою методу. Методи надають визначеному типу
певну поведінку.
type Celsius float64
func (c Celsius) Fahrenheit() float64 {
return float64(c)*9/5 + 32
}
func main() {
c := Celsius(100)
fmt.Println(c.Fahrenheit()) // 212
}
Отримувач (c Celsius) — це звичайний параметр; єдина відмінність — його позиція.
Всередині тіла методу c поводиться як будь-яка інша змінна типу Celsius.
З досвіду Python: ≈ метод класу, з однією тонкою відмінністю — класу немає. Отримувач — це просто додатковий параметр, який компілятор прив'язує до конкретного типу. Ви також самі даєте йому ім'я — ніякого неявного
self.
Отримувачі-значення¶
func (c Celsius) — це отримувач-значення (value receiver). Метод отримує
копію значення. Зміна отримувача всередині методу не впливає на оригінал.
type Counter int
func (c Counter) Inc() { // отримувач-значення — працює з копією
c++
}
func main() {
var n Counter = 0
n.Inc()
n.Inc()
fmt.Println(n) // 0 — Inc жодного разу не торкнувся n у викликача
}
Використовуйте отримувач-значення, коли:
- Метод не потребує змінювати отримувач.
- Отримувач невеликий (визначений тип на основі примітиву, невелика структура).
Отримувачі-вказівники¶
func (c *Counter) — це отримувач-вказівник (pointer receiver). Метод
отримує вказівник на оригінал, і записи через нього змінюють значення у викликача.
type Counter int
func (c *Counter) Inc() { // отримувач-вказівник — записує через вказівник
*c++
}
func main() {
var n Counter = 0
n.Inc()
n.Inc()
fmt.Println(n) // 2
}
Використовуйте отримувач-вказівник, коли:
- Метод повинен змінювати отримувач.
- Отримувач великий (структура з багатьма полями, яку не хочеться копіювати при кожному виклику).
- Структура містить поле, яке не можна копіювати (наприклад,
sync.Mutex). - Узгодженість: якщо хоча б один метод типу потребує отримувача-вказівника, зробіть всі методи з отримувачами-вказівниками, щоб набір методів типу був узгодженим.
Автоматичне взяття адреси та розіменування¶
Вам не потрібно писати (&n).Inc() чи (*p).Inc(). Go сам підставляє & або
*, коли в місці виклику є значення одного виду, а метод очікує інший.
type Counter int
func (c *Counter) Inc() { *c++ }
func main() {
var n Counter = 0
n.Inc() // Go непомітно переписує як (&n).Inc()
p := &n
p.Inc() // вже вказівник; переписування не потрібне
fmt.Println(n) // 2
}
Умова для переписування: значення повинно бути адресованим (іменована
змінна, поле адресованої структури або результат * чогось).
Елемент мапи або значення, що повертає функція, не є адресованим.
type Counter int
func (c *Counter) Inc() { *c++ }
m := map[string]Counter{"x": 0}
m["x"].Inc() // compile error: cannot call pointer method Inc on Counter
Рішення: зчитати в локальну змінну, змінити, записати назад; або змінити
мапу так, щоб вона зберігала значення *Counter.
Методи на нестruct-типах¶
Тип отримувача може бути будь-яким визначеним типом у вашому пакеті — не лише структурами.
type Names []string
func (n Names) Contains(s string) bool {
for _, x := range n {
if x == s {
return true
}
}
return false
}
func main() {
n := Names{"Ada", "Linus"}
fmt.Println(n.Contains("Ada")) // true
fmt.Println(n.Contains("Grace")) // false
}
Саме так до типів зрізу, мапи, функції або примітивних типів прикріплюється поведінка.
Обмеження «той самий пакет»¶
Тип отримувача повинен бути визначений у тому ж пакеті, що й метод:
package mine
func (t time.Time) Foo() { ... } // compile error
func (i int) Double() int { ... } // compile error
Не можна прикріпити методи до int, time.Time або чогось іншого з іншого
пакету. Рішення таке саме, як у
09-custom-types.md: визначте власний тип з чужим типом як
базовим типом і прикріпіть метод до нього.
Набори методів — попередній огляд¶
Кожен тип має набір методів (method set): методи, які можна викликати на значеннях цього типу. Правило:
- Набір методів
Tмістить усі методи з типом отримувачаT. - Набір методів
*Tмістить усі методи з типом отримувача*Tта всі методи з типом отримувачаT.
На практиці ви рідко явно думаєте про набори методів — аж поки не починаєте реалізовувати інтерфейси. Інтерфейс (детально розглядається в окремій статті) — це іменований набір сигнатур методів; тип задовольняє інтерфейс, коли його набір методів містить усі ці методи. Інтерфейси розглядатимуться окремо; запам'ятайте правило:
Якщо будь-який метод має отримувач-вказівник, тільки
*T(неT) задовольняє інтерфейси, що включають цей метод.
Значення методу та вирази методу¶
Метод можна від'єднати від його отримувача двома способами.
Значення методу — отримувач вбудований¶
type Celsius float64
func (c Celsius) Fahrenheit() float64 { return float64(c)*9/5 + 32 }
c := Celsius(100)
f := c.Fahrenheit // *значення* методу — c захоплено
fmt.Println(f()) // 212
f має тип func() float64. Отримувач c захоплений замиканням.
Вираз методу — отримувач є першим параметром¶
g має тип func(Celsius) float64. Отримувач стає явним першим параметром
у місці виклику.
Значення методу набагато поширеніші в реальному коді; вирази зустрічаються в допоміжних бібліотеках і тестах.
Вбудовування та просування методів¶
Якщо структура вбудовує інший тип (поле з назвою типу і без назви поля), методи вбудованого типу стають доступними для виклику на зовнішній структурі.
type Logger struct{ prefix string }
func (l Logger) Log(msg string) { fmt.Println(l.prefix, msg) }
type Server struct {
Logger // вбудовано — без назви поля
addr string
}
func main() {
s := Server{Logger: Logger{prefix: "[srv]"}, addr: ":8080"}
s.Log("starting") // [srv] starting
}
s.Log(...) — це скорочення для s.Logger.Log(...). Метод просунуто до
Server. Компонуйте поведінку за допомогою вбудовування; в Go немає наслідування.
Короткий довідник¶
| Потреба | Запис |
|---|---|
| Метод, що читає отримувач | func (c Celsius) F() float64 (отримувач-значення) |
| Метод, що змінює отримувач | func (c *Counter) Inc() (отримувач-вказівник) |
| Виклик методу з отримувачем-вказівником на змінній-значенні | Просто пишіть n.Inc() — Go підставить & |
| Метод на зрізі / мапі / типі на основі int | Визначте type X []int, потім func (x X) Foo() {} |
| Прив'язати метод до фіксованого отримувача | f := c.Fahrenheit (значення методу) |
| Використати метод як незв'язану функцію | g := Celsius.Fahrenheit (вираз методу) |