Tarkibga o'tish

Go’da metodlar

Metod — ma’lum turga bog‘langan funksiya. U ma’lumot va shu ma’lumot ustidagi amalni bir joyda ifodalashga yordam beradi. Oddiy funksiyadan farqi, metod nomidan oldin receiver yoziladi.

Value receiver

package main

import "fmt"

type Rectangle struct {
    Width  float64
    Height float64
}

func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

func main() {
    rectangle := Rectangle{Width: 5, Height: 3}
    fmt.Println("Yuza:", rectangle.Area())
}

Natija:

Yuza: 15

(r Rectangle) receiver bo‘lib, metod qaysi turga tegishli ekanini bildiradi. r qiymatning nusxasi. Faqat o‘qiydigan va kichik turlarda value receiver tabiiy tanlov bo‘lishi mumkin.

Metod faqat shu paketda e’lon qilingan nomlangan turga biriktiriladi. Boshqa paketdagi time.Time kabi turga bevosita yangi metod qo‘shib bo‘lmaydi.

Oddiy turga metod qo‘shish

Metod faqat struct uchun emas, shu paketda e’lon qilingan har qanday nomlangan tur uchun yozilishi mumkin:

package main

import "fmt"

type Celsius float64

func (c Celsius) Fahrenheit() float64 {
    return float64(c)*9/5 + 32
}

func main() {
    temperature := Celsius(25)
    fmt.Println(temperature.Fahrenheit())
}

Natija:

77

Celsiusning asosiy turi float64, lekin u alohida nomlangan tur. Shu sababli unga Fahrenheit() metodini biriktirish mumkin. Oddiy alias bunga yaramaydi:

type Celsius = float64

Bu yozuv yangi tur yaratmaydi. U faqat float64 uchun boshqa nom beradi.

Pointer receiver

Asl qiymat o‘zgarishi kerak bo‘lsa, pointer receiver ishlatiladi:

package main

import "fmt"

type Account struct {
    Balance int64
}

func (a *Account) Deposit(amount int64) error {
    if a == nil {
        return fmt.Errorf("account nil")
    }
    if amount <= 0 {
        return fmt.Errorf("amount musbat bo‘lishi kerak")
    }
    a.Balance += amount
    return nil
}

func main() {
    account := Account{Balance: 100_000}
    if err := account.Deposit(25_000); err != nil {
        fmt.Println("Xato:", err)
        return
    }
    fmt.Println(account.Balance)
}

Natija:

125000

a.Balance yozuvida Go avtomatik dereference qiladi; (*a).Balance deb yozish shart emas. account manzili olinadigan qiymat bo‘lgani uchun account.Deposit(...) chaqiruvini kompilyator (&account).Deposit(...) sifatida qabul qiladi.

Pointer receiver nil bilan chaqirilishi mumkin. Metodning vazifasiga qarab nil tekshiriladi yoki bu holat taqiqlanadi. Yuqoridagi metod xato qaytarib xavfsiz yakunlanadi.

Metod orqali holatni boshqarish

Metodlar struct maydonlarini bevosita ochiq qoldirmasdan boshqarishga yordam beradi. Masalan, hisob balansiga noto‘g‘ri qiymat yozilishini metod ichida cheklash mumkin:

package main

import (
    "errors"
    "fmt"
)

type Account struct {
    balance int64
}

func (a *Account) Deposit(amount int64) error {
    if amount <= 0 {
        return errors.New("summa musbat bo‘lishi kerak")
    }
    a.balance += amount
    return nil
}

func (a Account) Balance() int64 {
    return a.balance
}

func main() {
    account := Account{}
    if err := account.Deposit(50_000); err != nil {
        fmt.Println("Xato:", err)
        return
    }
    fmt.Println(account.Balance())
}

Natija:

50000

balance kichik harf bilan boshlangan, shuning uchun boshqa paket undan bevosita foydalana olmaydi. Qiymatni o‘zgartirish Deposit() orqali, o‘qish esa Balance() orqali bajariladi. Shu tariqa tekshiruv bir joyda saqlanadi.

Metodni chaqirish usullari

Metod odatda qiymat orqali chaqiriladi:

area := rectangle.Area()

Go metodni funksiya qiymati sifatida ham olish imkonini beradi. Method value receiverni o‘zida saqlaydi:

package main

import "fmt"

type Counter struct {
    value int
}

func (c *Counter) Add(n int) {
    c.value += n
}

func main() {
    counter := &Counter{}
    add := counter.Add

    add(3)
    add(2)
    fmt.Println(counter.value)
}

Natija:

5

add o‘zgaruvchisi counter receiveriga bog‘langan. Shu sababli uni chaqirganda receiver qayta berilmaydi.

Method expression esa receiverni oddiy birinchi argumentga aylantiradi:

add := (*Counter).Add
add(counter, 5)

Bu ko‘rinish bir metodni turli receiverlar bilan funksiya sifatida uzatish kerak bo‘lganda foydali.

Avtomatik manzil olish va ochish

Quyidagi ikki chaqiruv ko‘p holatda bir xil ishlaydi:

account := Account{}
account.Deposit(1000)
(&account).Deposit(1000)

account manzili olinadigan qiymat bo‘lgani uchun kompilyator pointer receiverli metod chaqirig‘ida &accountni avtomatik qo‘shadi. Pointerda value receiverli metod chaqirilsa, u ham avtomatik ochiladi:

accountPointer := &account
fmt.Println(accountPointer.Balance())

Bu qulaylik metodni chaqirishga tegishli. U Account va *Accountning metodlar to‘plamini bir xil qilib qo‘ymaydi.

Receiver tanlash

Pointer receiver tanlang, agar:

  • metod receiver holatini o‘zgartirsa;
  • struct katta bo‘lsa va nusxalashni kamaytirish muhim bo‘lsa;
  • turda sync.Mutex kabi nusxalanmasligi kerak bo‘lgan maydon bo‘lsa;
  • boshqa metodlar allaqachon pointer receiver ishlatsa va izchillik kerak bo‘lsa.

Value receiver kichik, o‘zgarmas qiymatga o‘xshash turlarda qulay. Receiver ichida reference semantikaga ega map yoki slice bo‘lsa, value receiver ham uning asosiy ma’lumotini o‘zgartira olishi mumkin. Shuning uchun tanlovni faqat “metod receiver maydoniga assignment qiladimi” bilan cheklamang.

Method set va interface bilan bog‘lanish

T turining metodlar to‘plamiga T receiverli metodlar kiradi. *T metodlar to‘plamiga esa T va *T receiverli metodlar kiradi. Natijada faqat pointer receiver bilan e’lon qilingan metodni talab qiladigan interface’ni odatda *T bajaradi, T emas.

Bu farq metodni oddiy value.Method() ko‘rinishida chaqirishdagi avtomatik & bilan aralashmasligi kerak. Interface’ga moslik method set orqali kompilyatsiya vaqtida tekshiriladi.

Quyidagi misolda Increment() faqat pointer receiver bilan e’lon qilingan:

package main

import "fmt"

type Incrementer interface {
    Increment()
}

type Counter int

func (c *Counter) Increment() {
    *c++
}

func run(value Incrementer) {
    value.Increment()
}

func main() {
    var counter Counter
    run(&counter)
    fmt.Println(counter)
}

Natija:

1

run(counter) ishlamaydi, chunki Counterning metodlar to‘plamida pointer receiverli Increment() yo‘q. run(&counter) ishlaydi, chunki *Counter metodlar to‘plamiga bu metod kiradi.

Receiver nomini tanlash

Receiver nomi odatda tur nomining bir yoki ikki harfidan olinadi:

func (a Account) Balance() int64
func (c *Counter) Increment()

this yoki self kabi nomlar Go uslubida odatda ishlatilmaydi. Bir tur metodlarida receiver nomini izchil saqlash kodni o‘qishni osonlashtiradi.

Xulosa

  • Metod receiver orqali nomlangan turga bog‘lanadi.
  • Value receiver nusxa bilan ishlaydi.
  • Pointer receiver asl qiymatni o‘zgartira oladi va nil bo‘lishi mumkin.
  • Receiver tanlovi semantika, izchillik va nusxalash xarajatiga asoslanadi.
  • T va *Tning method setlari interface’ga moslikda farq qiladi.
  • Method value receiverni saqlaydi, method expression esa receiverni argument sifatida qabul qiladi.

Keyingi darsda metodlar to‘plami orqali tur xatti-harakatini ifodalovchi interface ko‘rib chiqiladi.