Tarkibga o'tish

Go’da interface

Interface tur qanday ma’lumot saqlashini emas, qaysi metodlarga ega bo‘lishini belgilaydi. Kerakli metodlar to‘plamiga ega har qanday tur interface’ni avtomatik bajaradi. Buning uchun alohida implements kalit so‘zi yozilmaydi.

Bu yondashuv funksiyani aniq structga emas, kerakli xatti-harakatga bog‘laydi. Masalan, xabar yuboruvchi funksiya email yoki SMS turining ichki tuzilishini bilishi shart emas. Unga Send metodi mavjudligi kifoya.

Interface e’lon qilish va bajarish

package main

import "fmt"

type Sender interface {
    Send(message string) error
}

type EmailSender struct {
    Address string
}

func (e EmailSender) Send(message string) error {
    if e.Address == "" {
        return fmt.Errorf("email manzili bo‘sh")
    }
    fmt.Printf("%s manziliga: %s\n", e.Address, message)
    return nil
}

func Notify(sender Sender, message string) error {
    return sender.Send(message)
}

func main() {
    email := EmailSender{Address: "[email protected]"}
    if err := Notify(email, "Buyurtma tayyor"); err != nil {
        fmt.Println("Xato:", err)
    }
}

Natija:

[email protected] manziliga: Buyurtma tayyor

EmailSenderda aynan Send(string) error metodi borligi uchun u Sender interface’iga mos keladi. Buni kompilyatsiya vaqtida alohida tekshirish uchun quyidagi yozuvdan foydalanish mumkin:

var _ Sender = EmailSender{}

Faqat metod nomining bir xil bo‘lishi yetarli emas. Parametrlar va qaytish turlari ham aynan mos bo‘lishi kerak.

Kichik interface’lar

Interface’ni odatda undan foydalanuvchi paket e’lon qiladi va faqat o‘ziga kerakli metodlarni talab qiladi. Bir yoki bir nechta metoddan iborat kichik interface’larni bajarish, testlarda boshqa tur bilan almashtirish va qayta ishlatish osonroq. Standart kutubxonadagi io.Reader faqat Read metodini talab qilishi bunga yaxshi misoldir.

Bir interface ichiga boshqa interface’ni joylashtirib, ularning metodlar to‘plamini birlashtirish mumkin:

type Reader interface {
    Read([]byte) (int, error)
}

type Writer interface {
    Write([]byte) (int, error)
}

type ReadWriter interface {
    Reader
    Writer
}

Dynamic type va dynamic value

Interface qiymatini ikki qism orqali tushuntirish mumkin:

  • dynamic type — interface ichida saqlanayotgan qiymatning aniq turi;
  • dynamic value — shu aniq turdagi qiymat.
package main

import "fmt"

func main() {
    var value any = 42
    fmt.Printf("Tur: %T, qiymat: %v\n", value, value)

    value = "Go"
    fmt.Printf("Tur: %T, qiymat: %v\n", value, value)
}

Natija:

Tur: int, qiymat: 42
Tur: string, qiymat: Go

anyinterface{}ning aliasi. U istalgan turdagi qiymatni qabul qiladi, ammo aniq xatti-harakatni ifodalamaydi. Imkon bo‘lsa, kerakli metodlarni talab qiladigan interface yoki generics yordamida tur xavfsizligini (type safety) saqlang.

nil interface tuzog‘i

Interface faqat dynamic type va dynamic value ikkalasi ham mavjud bo‘lmasa, nilga teng bo‘ladi:

package main

import "fmt"

type MyError struct{}

func (e *MyError) Error() string { return "xato" }

func main() {
    var pointer *MyError
    var err error = pointer

    fmt.Println(pointer == nil)
    fmt.Println(err == nil)
    fmt.Printf("%T %v\n", err, err)
}

Natija:

true
false
*main.MyError xato

err ichida *MyError dynamic turi bor. Shu sabab uning dynamic qiymati nil bo‘lsa ham, interface’ning o‘zi nil emas. Xato qaytaruvchi funksiya muvaffaqiyat holatida typed nil pointer emas, to‘g‘ridan-to‘g‘ri nil qaytarishi kerak.

Type assertion va type switch

Interface ichidagi qiymatning aniq turi kerak bo‘lsa, type assertion yoki type switch ishlatiladi:

package main

import "fmt"

func describe(value any) {
    switch v := value.(type) {
    case int:
        fmt.Println("Butun son:", v)
    case string:
        fmt.Println("Satr:", v)
    default:
        fmt.Printf("Boshqa tur: %T\n", v)
    }
}

func main() {
    describe(42)
    describe("Go")
}

Natija:

Butun son: 42
Satr: Go

Bitta turni tekshirish uchun v, ok := value.(string) shakli ishlatiladi. Ikkinchi qiymat olinmaydigan v := value.(string) ko‘rinishida aniq tur mos kelmasa, type assertion panic keltirib chiqaradi.

Pointer receiver ta’siri

Agar interface talab qiladigan metod *T receiver bilan e’lon qilingan bo‘lsa, interface’ni *T bajaradi, T esa bajarmaydi. Bu metodlar to‘plami (method set) qoidasidir. Interface qiymatiga tayinlashda kompilyator oddiy metod chaqiruvidagidek avtomatik manzil olmaydi.

Xulosa

  • Interface metodlar to‘plamini ifodalaydi va turlar tomonidan yashirin bajariladi.
  • Kichik, iste’molchi ehtiyojiga mos interface’lar afzal.
  • Interface qiymatida dynamic type va dynamic value bor.
  • Typed nil pointer saqlagan interface nil emas.
  • Type assertion’da comma ok shakli tur mos kelmaganda panicning oldini oladi.

Keyingi darsda Go’da alohida enum kalit so‘zi bo‘lmasa ham, nomlangan tur va iota bilan cheklangan konstantalar yaratish o‘rganiladi.

Misollar

1. Kichik interface orqali turli shakllar

package main

import "fmt"

type Yuzali interface{ Yuza() float64 }
type Kvadrat struct{ Tomon float64 }
type Tortburchak struct{ Eni, Boyi float64 }

func (k Kvadrat) Yuza() float64 {
    return k.Tomon * k.Tomon
}

func (t Tortburchak) Yuza() float64 {
    return t.Eni * t.Boyi
}

func chiqar(y Yuzali) {
    fmt.Println(y.Yuza())
}

func main() {
    chiqar(Kvadrat{Tomon: 4})
    chiqar(Tortburchak{Eni: 3, Boyi: 5})
}

Ikkala turda ham Yuza() metodi bor. Shu sabab ular Yuzali interface’ini yashirin bajaradi.

2. Interface’lar slice’i

package main

import "fmt"

type Nomli interface{ Nom() string }
type Shahar string
type Til string

func (s Shahar) Nom() string { return string(s) }
func (t Til) Nom() string    { return string(t) }

func main() {
    qiymatlar := []Nomli{Shahar("Toshkent"), Til("Go")}
    for _, qiymat := range qiymatlar {
        fmt.Println(qiymat.Nom())
    }
}

Bitta slice umumiy interface orqali turli aniq turlarni saqlaydi. Sikl qiymatlarning aniq turiga emas, faqat interface metodiga tayanadi.

3. Funksiya parametrida kichik interface

package main

import "fmt"

type Tekshiruvchi interface{ Togri() bool }
type Ball int

func (b Ball) Togri() bool {
    return b >= 0 && b <= 100
}

func tekshir(t Tekshiruvchi) bool {
    return t.Togri()
}

func main() {
    fmt.Println(tekshir(Ball(86)))
}

Funksiya aniq turga emas, faqat o‘ziga kerakli Togri() xatti-harakatiga bog‘langan.

4. Bo‘sh interface’da tur assertion’i

package main

import "fmt"

func main() {
    var qiymat any = "Go"
    matn, ok := qiymat.(string)
    fmt.Println(matn, ok)
}

comma ok shakli dynamic tur string ekanini panicsiz tekshiradi. Tur mos kelmasa, ok qiymati false bo‘ladi.

5. Type switch bilan qiymatlarni ajratish

package main

import "fmt"

func chiqar(qiymat any) {
    switch v := qiymat.(type) {
    case int:
        fmt.Println("Butun son:", v)
    case string:
        fmt.Println("Matn:", v)
    case bool:
        fmt.Println("Mantiqiy qiymat:", v)
    default:
        fmt.Println("Noma’lum tur")
    }
}

func main() {
    chiqar(42)
    chiqar("Go")
}

Type switch dynamic turga mos keladigan caseni tanlaydi. v tanlangan blok ichida shu aniq turga ega bo‘ladi.

6. Interface’ning nil holati

package main

import "fmt"

func main() {
    var qiymat any
    fmt.Println(qiymat == nil)
    qiymat = 0
    fmt.Println(qiymat == nil)
}

Interface dynamic tur va qiymatga ega bo‘lmagandagina nil bo‘ladi. Ichida 0 qiymatli int saqlansa, interface’ning o‘zi nil bo‘lmaydi.

7. Typed nil pointer tuzog‘i

package main

import "fmt"

type Nomli interface{ Nom() string }
type Shaxs struct{ Ism string }

func (s *Shaxs) Nom() string {
    if s == nil {
        return "noma’lum"
    }
    return s.Ism
}

func main() {
    var shaxs *Shaxs
    var nomli Nomli = shaxs
    fmt.Println(nomli == nil, nomli.Nom())
}

nomli ichida *Shaxs dynamic turi borligi uchun u nil emas. Metod esa nil qabul qiluvchini xavfsiz boshqaradi.

8. Pointer receiver va method set

package main

import "fmt"

type Oshuvchi interface{ Oshirish() }
type Hisoblagich struct{ Son int }

func (h *Hisoblagich) Oshirish() { h.Son++ }

func main() {
    h := Hisoblagich{}
    var oshuvchi Oshuvchi = &h
    oshuvchi.Oshirish()
    fmt.Println(h.Son)
}

Oshirish() pointer receiver bilan e’lon qilingan. Shu sabab interface’ni Hisoblagich emas, *Hisoblagich bajaradi.

9. Bir interface’ni boshqasiga joylash

package main

import "fmt"

type Nomli interface{ Nom() string }
type Tafsilotli interface {
    Nomli
    Tavsif() string
}

type Mahsulot struct{ Nomi string }

func (m Mahsulot) Nom() string    { return m.Nomi }
func (m Mahsulot) Tavsif() string { return "Mahsulot: " + m.Nomi }

func main() {
    var qiymat Tafsilotli = Mahsulot{Nomi: "Kitob"}
    fmt.Println(qiymat.Nom(), qiymat.Tavsif())
}

Tafsilotli interface’i Nomli talab qiladigan metodlarni o‘z ichiga oladi va ularga qo‘shimcha ravishda Tavsif() metodini talab qiladi.

10. Interface qiymatini boshqa interface’ga uzatish

package main

import "fmt"

type Stringer interface{ String() string }
type Kod int

func (k Kod) String() string { return fmt.Sprintf("KOD-%d", k) }

func chopEt(s Stringer) { fmt.Println(s.String()) }

func main() {
    var s Stringer = Kod(42)
    chopEt(s)
}

Interface qiymati kerakli metodlar to‘plamini talab qiladigan parametrga uzatiladi. Bunda uning dynamic turi Kod, dynamic qiymati esa Kod(42) bo‘lib qoladi.