Goda interface
Interface — tur qanday ma'lumot saqlashini emas, u qanday xatti-harakatni bajara olishini belgilaydi.
Boshqacha aytganda, interface ichida struct maydonlari yozilmaydi. Uning o'rniga turda qaysi metodlar bo'lishi kerakligi ko'rsatiladi.
Masalan, bizga xabar yubora oladigan tur kerak bo'lsin.
Buning uchun turning:
- email manzilini qanday saqlashi;
- telefon raqamini qayerda saqlashi;
- HTTP API ishlatishi;
- SMTP ishlatishi;
biz uchun muhim bo'lmasligi mumkin.
Bizga faqat bitta narsa kerak:
metodi mavjud bo'lishi.
Shunda quyidagicha interface e'lon qilish mumkin:
Endi Send(string) error metodiga ega bo'lgan har qanday tur Sender interface'iga mos keladi.
Go'da buning uchun Java yoki C# kabi tillardagi:
kalit so'zini yozish kerak emas.
Masalan, quyidagicha yozilmaydi:
Go bunga ehtiyoj sezmaydi.
Agar EmailSender turida interface talab qilayotgan barcha metodlar mavjud bo'lsa, Go uni avtomatik ravishda shu interface'ni bajaradi deb hisoblaydi.
Bu implicit interface implementation, ya'ni interface'ni yashirin bajarish deyiladi.
Interface'ning asosiy foydalaridan biri — kodni aniq bir structga bog'lab qo'ymaslik.
Masalan, quyidagi funksiya:
EmailSender, SMSSender, TelegramSender yoki boshqa aniq turni talab qilmaydi.
U faqat:
interface'ini talab qiladi.
Demak, funksiyaga beriladigan qiymat:
"Sen qaysi structsan?"
degan talabga emas:
"Sen
Send()metodini bajara olasanmi?"
degan talabga javob berishi kerak.
Bu interface'larning eng muhim g'oyalaridan biridir.
Interface e'lon qilish va bajarish
Quyidagi misolni ko'ramiz:
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
Kodning muhim qismlarini alohida ko'rib chiqamiz.
Avval interface:
Bu kod:
Senderbo'lishni istagan turdaSend(string) errormetodi bo'lishi kerak
degan talabni bildiradi.
Keyin EmailSender turi bor:
Bu oddiy struct.
Uning ichida email manzili saqlanadi.
Keyin unga metod yozilgan:
Bu metodning signature'i interface ichidagi metod bilan aynan bir xil:
Shuning uchun:
Sender interface'ini avtomatik bajaradi.
Biz hech qayerda:
deb alohida ko'rsatmadik.
Go metodlar to'plamiga qarab buni o'zi aniqlaydi.
Shuning uchun quyidagi kod ishlaydi:
email := EmailSender{
Address: "[email protected]",
}
Notify(email, "Buyurtma tayyor")
Notify() funksiyasi esa quyidagicha yozilgan:
Bu funksiya EmailSender haqida hech narsa bilmaydi.
U:
maydonini ham bilmaydi.
Unga email qanday yuborilishi ham muhim emas.
Funksiya faqat bitta narsani biladi:
chaqirish mumkin.
Shuning uchun keyinchalik boshqa tur yozishimiz mumkin:
va unga:
metodini qo'shsak, SMSSender ham avtomatik ravishda Senderga mos keladi.
Notify() funksiyasini esa o'zgartirish shart emas:
Ikkalasi ham ishlashi mumkin.
Interface bajarilishini kompilyatsiya vaqtida tekshirish
Ba'zan ma'lum bir tur interface'ni bajarishini kodning o'zida aniq tekshirib qo'yish foydali bo'ladi.
Buning uchun Go'da quyidagi usul ko'p ishlatiladi:
Bu yerda _ — blank identifier.
Bizga qiymatning o'zi kerak emas.
Bu yozuv orqali kompilyatorga:
EmailSenderqiymatiniSendersifatida ishlatish mumkinligini tekshir
deyapmiz.
Agar EmailSender Sender interface'iga mos kelmasa, kod kompilyatsiyadan o'tmaydi.
Masalan, metod noto'g'ri yozilgan bo'lsa:
bu Sender interface'iga mos kelmaydi.
Sababi interface:
metodini talab qiladi.
Bizning metod esa:
bo'lib qolgan.
Demak, faqat metod nomining bir xil bo'lishi yetarli emas.
Quyidagilarning barchasi mos kelishi kerak:
- metod nomi;
- parametrlar soni;
- parametrlarning turlari;
- parametrlar tartibi;
- qaytariladigan qiymatlar;
- qaytariladigan qiymatlarning turlari.
Masalan:
va:
ikki xil metod signature hisoblanadi.
Shuning uchun ikkinchisi birinchisini talab qiladigan interface'ni bajarmaydi.
Kichik interface'lar
Go'da odatda kichik interface'lar afzal ko'riladi.
Ya'ni interface ichiga imkon qadar faqat haqiqatan kerak bo'lgan metodlar yoziladi.
Masalan, funksiyaga faqat ma'lumot o'qish kerak bo'lsa, unga quyidagicha katta interface berish shart emas:
Agar funksiya faqat o'qisa, unga bitta metod yetarli bo'lishi mumkin:
Buning bir nechta foydasi bor.
Birinchidan, kichik interface'ni bajarish oson.
Agar interface bitta metod talab qilsa:
yangi turga faqat shu metodni qo'shish yetarli.
Agar interface o'nta metod talab qilsa, uni bajarish uchun turning o'nta metodga ega bo'lishi kerak.
Ikkinchidan, kichik interface test yozishni osonlashtiradi.
Masalan, testda haqiqiy email servis o'rniga kichik test turi yaratish mumkin:
FakeSender Sender interface'iga mos keladi.
Shuning uchun testda haqiqiy email yuborishga hojat qolmaydi.
Uchinchidan, kichik interface qayta ishlatishga qulayroq.
Go standart kutubxonasidagi eng mashhur misollardan biri:
interface'idir.
U faqat bitta metod talab qiladi:
Shunga qaramay, juda ko'p tur io.Reader sifatida ishlatilishi mumkin:
- fayl;
- HTTP response body;
- buffer;
- TCP ulanish;
- siqilgan ma'lumot oqimi;
- xotiradagi satr.
Bularning ichki tuzilishi turlicha.
Lekin barchasi:
orqali ma'lumot bera olsa, io.Reader bilan ishlaydigan kod ulardan foydalanishi mumkin.
Interface'ni ko'pincha undan foydalanuvchi tomon e'lon qiladi
Go'da muhim amaliy tamoyillardan biri:
Interface'ni ko'pincha uni amalga oshiruvchi tur emas, undan foydalanuvchi kod e'lon qiladi.
Masalan, EmailSender paketining o'zi katta:
interface e'lon qilishi shart emas.
Agar boshqa paketga faqat:
kerak bo'lsa, o'sha paket o'zining kichik interface'ini yaratishi mumkin.
Bu komponentlarni bir-biriga kamroq bog'laydi.
Interface ichiga boshqa interface'ni joylash
Go'da bitta interface ichiga boshqa interface'ni joylashtirish mumkin.
Bu interface embedding deyiladi.
Masalan:
type Reader interface {
Read([]byte) (int, error)
}
type Writer interface {
Write([]byte) (int, error)
}
Bu yerda ikkita alohida interface bor.
Reader ma'lumot o'qiy oladigan turni bildiradi.
Writer esa ma'lumot yoza oladigan turni bildiradi.
Endi ikkalasini birlashtirish mumkin:
Bu yozuvning ma'nosi:
ReadWriterbo'lish uchun hamReader, hamWritertalablarini bajarish kerak.
Ya'ni amalda ReadWriter quyidagiga teng:
Lekin interface'larni embedding qilish orqali mavjud kichik interface'lardan yangi interface tuzish mumkin.
Masalan, bir turda faqat:
bo'lsa, u Readerni bajaradi.
Agar faqat:
bo'lsa, u Writerni bajaradi.
Agar ikkalasi ham bo'lsa:
u ReadWriterni ham bajaradi.
Bu usul kichik interface'larni birlashtirib kattaroq xatti-harakat yaratishga yordam beradi.
Dynamic type va dynamic value
Interface'ni to'g'ri tushunish uchun uning ichida qiymat qanday saqlanishini bilish muhim.
Interface qiymatini soddalashtirib ikki qism orqali tasavvur qilish mumkin:
- dynamic type;
- dynamic value.
Dynamic type nima?
Dynamic type — interface ichida hozir saqlanayotgan qiymatning aniq turi.
Masalan:
Bu yerda o'zgaruvchining statik turi:
lekin uning ichidagi qiymatning aniq turi:
Demak, dynamic type:
bo'ladi.
Dynamic value nima?
Dynamic value esa interface ichida saqlanayotgan haqiqiy qiymat.
Yuqoridagi misolda:
dynamic type:
dynamic value:
bo'ladi.
Quyidagi misolni ko'ramiz:
package main
import "fmt"
func main() {
var value any = 15
fmt.Printf("Tur: %T, qiymat: %v\n", value, value)
value = "Go"
fmt.Printf("Tur: %T, qiymat: %v\n", value, value)
}
Natija:
Avval:
bo'lganda interface ichida:
saqlanadi.
Keyin:
deb qiymatni almashtirdik.
Endi interface ichida:
saqlanadi.
Interface o'zgaruvchisining o'zi hamon:
turida.
Lekin uning ichidagi aniq tur runtime davomida o'zgarishi mumkin.
any nima?
Go'da:
quyidagining aliasidir:
Ya'ni:
va:
bir xil ma'noni anglatadi.
Bo'sh interface:
hech qanday metod talab qilmaydi.
Go'dagi barcha turlar hech bo'lmaganda "nol dona metod talab qilish" shartiga mos keladi.
Shuning uchun any istalgan turdagi qiymatni saqlashi mumkin:
Bularning barchasi mumkin.
Lekin anyning kamchiligi ham shunda.
Masalan:
deb yozsak, funksiya value bilan nima qilish mumkinligini oldindan bilmaydi.
U int bo'lishi ham mumkin.
string bo'lishi ham mumkin.
Struct yoki slice bo'lishi ham mumkin.
Shuning uchun imkon bo'lgan joyda:
o'rniga kerakli xatti-harakatni bildiradigan aniq interface ishlatish yaxshiroq.
Masalan:
Bu anydan ancha kuchliroq talab beradi.
Funksiya ichida kamida:
chaqirish mumkinligini bilamiz.
Ayrim holatlarda interface o'rniga generics ham yaxshiroq type safety berishi mumkin.
Lekin interface va generics bir xil vazifani bajarmaydi.
Interface ko'proq xatti-harakatga:
"Bu qiymat nima qila oladi?"
degan savolga javob beradi.
nil interface tuzog'i
Interface bilan ishlaganda eng ko'p chalkashlik tug'diradigan mavzulardan biri — nil.
Oddiy pointerda vaziyat tushunarli:
Unga qiymat berilmagan bo'lsa:
natija:
bo'ladi.
Interface esa biroz boshqacha ishlaydi.
Interface'ni quyidagi ikki qism orqali tasavvur qilgan edik:
Interface faqat ikkala qism ham mavjud bo'lmaganda nil hisoblanadi.
Ya'ni haqiqiy nil interface taxminan:
ko'rinishida bo'ladi.
Quyidagi misolni ko'ramiz:
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:
Birinchi qarashda bu g'alati ko'rinishi mumkin.
Bizda:
bor.
Unga hech qanday qiymat berilmagan.
Demak:
natijasi:
Bu to'g'ri.
Keyin:
deb pointer'ni error interface'iga joyladik.
MyError error interface'ini bajaradi, chunki unda:
metodi bor.
Lekin err ichida endi ma'lumot mavjud.
Uning holatini quyidagicha tasavvur qilish mumkin:
Ya'ni:
Interface butunlay nil bo'lishi uchun esa:
bo'lishi kerak edi.
Lekin bizda dynamic type mavjud:
Shuning uchun:
natijasi:
bo'ladi.
Bu typed nil muammosi deb ataladigan holatlardan biri.
error qaytaruvchi funksiyada typed nil muammosi
Quyidagi kabi kod xavfli bo'lishi mumkin:
Dasturchi:
errpointernil, demak funksiyanilqaytaradi
deb o'ylashi mumkin.
Lekin funksiya qaytarayotgan turi:
interface.
*MyError pointer error interface'iga joylanganda dynamic type saqlanadi.
Natijada qaytgan interface:
ko'rinishiga ega bo'ladi.
Shuning uchun:
holati yuz beradi.
Muvaffaqiyat holatida error qaytaruvchi funksiya to'g'ridan-to'g'ri:
qaytarishi kerak.
Masalan:
Bu holatda interface haqiqatan:
bo'ladi.
Type assertion
Interface ichida aniq qaysi turdagi qiymat saqlanayotganini olish kerak bo'lishi mumkin.
Buning uchun type assertion ishlatiladi.
Masalan:
Biz value ichidagi qiymat string ekanini bilmoqchimiz.
Quyidagicha yozish mumkin:
Bu:
valueichidagi dynamic typestringbo'lsa, uning qiymatini ol
degan ma'noni anglatadi.
Agar value haqiqatan:
saqlayotgan bo'lsa:
natijada:
bo'ladi.
Lekin bu usulning xavfli tomoni bor.
Agar:
bo'lsa va:
deb yozsak, dastur panic qiladi.
Sababi dynamic type:
lekin biz:
deb talab qilyapmiz.
comma ok shakli
Type assertion'ni xavfsizroq ishlatish uchun odatda:
ko'rinishi ishlatiladi.
Masalan:
Agar dynamic type string bo'lsa:
bo'ladi.
Agar:
bo'lsa:
natijada:
bo'ladi.
Dastur panic qilmaydi.
textga string turning zero value qiymati beriladi:
Shuning uchun interface ichidagi turni tekshirishda ko'pincha:
usuli xavfsizroq.
Type switch
Agar interface ichidagi qiymat bir nechta turdan biri bo'lishi mumkin bo'lsa, ketma-ket ko'p type assertion yozish noqulay.
Masalan:
if v, ok := value.(int); ok {
...
}
if v, ok := value.(string); ok {
...
}
if v, ok := value.(bool); ok {
...
}
Bunday holat uchun Go'da type switch mavjud.
Misol:
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(15)
describe("Go")
}
Natija:
Type switch'ning asosiy qismi:
Oddiy type assertion'da:
deb aniq turni yozamiz.
Type switch'da esa maxsus:
ko'rinishi ishlatiladi.
Keyin kerakli turlar case orqali tekshiriladi:
Agar dynamic type int bo'lsa, shu blok bajariladi.
Agar dynamic type string bo'lsa, shu blok bajariladi.
Masalan:
chaqirilganda:
Shuning uchun:
tanlanadi.
Bu blok ichida vning turi ham:
bo'ladi.
Shuning uchun:
deb to'g'ridan-to'g'ri ishlatish mumkin.
describe("Go") chaqirilganda esa v string bo'ladi.
Agar hech bir case mos kelmasa:
bloki bajariladi.
Type switch ayniqsa any bilan ishlaydigan kodlarda foydali.
Lekin uni keragidan ortiq ishlatish ham yaxshi emas.
Agar funksiyaning ishlashi uchun ma'lum bir metod kerak bo'lsa, any olib keyin tur tekshirishdan ko'ra aniq interface yozish ko'pincha tushunarliroq.
Pointer receiverning interface'ga ta'siri
Oldingi metodlar mavzusida method set tushunchasini ko'rgan edik.
Bu interface bilan ishlaganda ayniqsa muhim.
Faraz qilamiz:
va metod:
Bu metodning receiveri:
Ya'ni pointer receiver.
Endi interface:
bo'lsin.
Savol:
interface'ni bajaradimi?
Yo'q.
Lekin:
bajaradi.
Sababi method set qoidasi.
Soddalashtirib:
method setiga value receiver bilan yozilgan metodlar kiradi.
method setiga esa:
Treceiverli metodlar;*Treceiverli metodlar;
kiradi.
Bizda:
bo'lganligi sabab Increment() *Counter method setiga kiradi.
Shuning uchun:
ishlaydi.
Lekin:
kompilyatsiyadan o'tmaydi.
Bu joyda Go'ning metod chaqirishdagi avtomatik & qo'shishi bilan interface qoidasini aralashtirmaslik kerak.
Masalan:
ishlashi mumkin.
Go counterning manzilini olish mumkinligini ko'rib, chaqiruvni amalda:
ko'rinishiga moslashtiradi.
Lekin:
degan interface assignment vaqtida Go avtomatik:
qilib bermaydi.
Interface'ga moslik turning haqiqiy method seti asosida tekshiriladi.
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 float64
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,
})
}
Bu misolda:
interface faqat bitta metod talab qilmoqda:
Kvadratda ham:
metodi mavjud.
Tortburchakda ham:
metodi mavjud.
Shuning uchun ikkala tur ham Yuzali interface'ini avtomatik bajaradi.
Biz hech qayerda:
yoki:
deb yozmadik.
Metodlar mosligi yetarli.
chiqar() funksiyasi:
aniq Kvadrat yoki Tortburchakni talab qilmaydi.
U faqat:
metodini chaqira olishni xohlaydi.
Shuning uchun:
ham:
ham ishlaydi.
Funksiya uchun shaklning ichida qanday maydonlar borligi muhim emas.
Unga faqat yuzani hisoblash qobiliyati kerak.
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())
}
}
Oddiy slice odatda bitta turdagi qiymatlarni saqlaydi.
Masalan:
faqat string saqlaydi.
faqat int saqlaydi.
Lekin bu misolda:
interface slice ishlatilgan.
Shahar va Til ikki xil aniq tur:
Lekin ikkalasida ham:
metodi bor.
Shuning uchun ikkalasi ham:
interface'ini bajaradi.
Natijada bitta slice ichida:
va:
qiymatlarini birga saqlash mumkin.
Sikl:
ichida kod qiymatning:
yoki:
ekaniga qiziqmaydi.
U faqat Nom() metodini chaqiradi.
Bu polymorphismning sodda ko'rinishlaridan biridir.
Turli aniq turlar bitta umumiy xatti-harakat orqali ishlatilmoqda.
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)))
}
Bu yerda:
juda kichik interface.
U faqat:
metodini talab qiladi.
Ball turida:
metodi bor.
Shuning uchun Ball avtomatik ravishda Tekshiruvchiga mos keladi.
tekshir() funksiyasi:
aniq Ball turiga bog'lanmagan.
Uning uchun qiymatning nima ekanligi muhim emas.
U faqat:
chaqira olishi kerak.
Keyinchalik boshqa tur yaratsak:
va unga:
metodini yozsak, Yosh ham shu funksiyaga berilishi mumkin:
tekshir() funksiyasini o'zgartirish shart emas.
Bu kichik interface'larning asosiy foydalaridan biri.
4. Bo'sh interface'da type assertion
package main
import "fmt"
func main() {
var qiymat any = "Go"
matn, ok := qiymat.(string)
fmt.Println(matn, ok)
}
Bu yerda:
interface ichida:
saqlanmoqda.
Keyin:
type assertion bajarildi.
Bu:
qiymatichidagi aniq turstringmi?
degan tekshiruv.
Haqiqatan string bo'lganligi sabab:
bo'ladi.
Natija taxminan:
bo'ladi.
Agar qiymat:
bo'lganida:
natijasi:
bo'lardi.
Eng muhim jihati — dastur panic qilmaydi.
Agar:
ko'rinishidan foydalanilganida va dynamic type string bo'lmaganida, runtime panic yuz berardi.
Shuning uchun tur mos kelmasligi mumkin bo'lgan joylarda comma ok shakli xavfsizroq.
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(15)
chiqar("Go")
}
Bu funksiya:
qabul qiladi.
Demak, unga turli turlarni berish mumkin.
Masalan:
Funksiya ichidagi:
dynamic type'ni tekshiradi.
Agar qiymat:
bo'lsa, dynamic type:
bo'ladi.
Shuning uchun:
bloki bajariladi.
Bu blok ichidagi v ham int turiga ega.
Agar:
chaqirilsa:
tanlanadi.
Bu safar v:
turida bo'ladi.
Agar:
chaqirilsa:
ishlaydi.
Hech bir case mos kelmasa:
bajariladi.
Type switch bir interface ichidagi bir nechta ehtimoliy aniq turni tartibli tekshirish uchun qulay.
6. Interface'ning nil holati
package main
import "fmt"
func main() {
var qiymat any
fmt.Println(qiymat == nil)
qiymat = 0
fmt.Println(qiymat == nil)
}
Avval:
deb interface e'lon qilindi.
Unga hech qanday qiymat berilmadi.
Uni soddalashtirib:
deb tasavvur qilish mumkin.
Shuning uchun:
natijasi:
bo'ladi.
Keyin:
deb yozdik.
Ba'zan 0 "bo'sh qiymat"dek ko'rinishi mumkin.
Lekin 0 — haqiqiy int qiymat.
Endi interface ichida:
saqlanmoqda.
Shuning uchun:
natijasi:
bo'ladi.
Interface ichidagi qiymatning zero value bo'lishi interface'ning o'zi nil degani emas.
Masalan, quyidagilarning barchasi nil bo'lmagan interface qiymatlaridir:
Ularning dynamic qiymatlari o'z turlarining zero value qiymati.
Lekin dynamic type mavjud.
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())
}
Bu misolda:
e'lon qilingan.
Unga qiymat berilmagan.
Shuning uchun:
bo'ladi.
Lekin keyin:
deb pointer interface ichiga joylandi.
*Shaxs Nomli interface'ini bajaradi, chunki unda:
metodi mavjud.
Endi nomli interface'ining holatini:
deb tasavvur qilish mumkin.
Dynamic type mavjud bo'lganligi uchun:
natija:
bo'ladi.
Keyin:
chaqirilmoqda.
Asl receiver ichidagi pointer nil.
Lekin metod boshida:
tekshiruvi bor.
Shuning uchun metod nil receiverni xavfsiz boshqaradi va:
qaytaradi.
Bu misol interface'dagi typed nil holatini yaxshi ko'rsatadi.
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)
}
Interface:
Oshirish() metodini talab qiladi.
Hisoblagichda metod:
ko'rinishida yozilgan.
Receiver:
ya'ni pointer receiver.
Shuning uchun Oshuvchi interface'ini:
bajaradi.
Shu sabab quyidagi kod to'g'ri:
&hning turi:
bo'ladi.
Lekin quyidagisi ishlamaydi:
Sababi Hisoblagichning o'z method setida pointer receiver bilan yozilgan Oshirish() mavjud emas.
Bu holat oddiy metod chaqiruvidan farq qiladi.
Quyidagisi ishlashi mumkin:
Chunki Go uni avtomatik:
ko'rinishiga moslashtiradi.
Lekin interface assignment vaqtida bunday avtomatik manzil olish bajarilmaydi.
Interface'ga moslik method set orqali tekshiriladi.
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(),
)
}
Avval:
interface mavjud.
Keyin:
deb yangi interface yaratildi.
Bu yerda:
interface Tafsilotli ichiga embedding qilingan.
Demak, Tafsilotli quyidagi metodlarni talab qiladi:
Buni to'liq yozganda quyidagiga teng deb tasavvur qilish mumkin:
Mahsulot turida ikkala metod ham mavjud:
va:
Shuning uchun Mahsulot:
interface'ini bajaradi.
Bundan tashqari, u:
interface'ini ham bajaradi.
Interface embedding katta interface'larni kichik va tushunarli interface'lardan yig'ish imkonini beradi.
Masalan:
type Reader interface {
Read(...)
}
type Writer interface {
Write(...)
}
type Closer interface {
Close() error
}
keyin ularni birlashtirib:
kabi interface yaratish mumkin.
10. Interface qiymatini boshqa interface parametriga 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(15)
chopEt(s)
}
Bu yerda:
interface mavjud.
Kod turi:
va unga:
metodi yozilgan.
Shuning uchun Kod Stringer interface'ini bajaradi.
Keyin:
deb interface qiymati yaratildi.
Bu paytda sni quyidagicha tasavvur qilish mumkin:
Keyin:
chaqirilmoqda.
chopEt() ham:
qabul qiladi:
Shuning uchun mavjud interface qiymatini unga to'g'ridan-to'g'ri berish mumkin.
Funksiya ichida:
chaqirilganda interface ichidagi dynamic type:
ekanligi sabab Kod.String() metodi ishlaydi.
Ya'ni:
chaqiriladi.
Natijada:
chiqariladi.
Interface qiymatini boshqa joyga uzatish uning ichidagi aniq qiymatni yo'qotmaydi.
Bu misolda uzatilgandan keyin ham:
bo'lib qoladi.
Funksiya esa aniq Kod turini bilishi shart emas.
U faqat:
metodidan foydalanadi.