Məzmuna keç
Educora
Orta18 dəq7 / 10

İnterfeyslər və generiklər

Gizli ödənilən interfeyslər, `any`, tip təsdiqi və tip switch-i ilə işlə, generik funksiyalar və tiplər yaz.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • İnterfeys elan etmək və onun gizli ödənilməsini izah etmək
  • any qiymətinin real tipini tip təsdiqi və tip switch-i ilə öyrənmək
  • Tip parametrləri və məhdudiyyətlərlə generik funksiya və tip yazmaq

Divardakı elektrik yuvasına telefon adapterini də, çaydanı da, lampanı da taxmaq olar: yuva cihazın nə olduğu ilə maraqlanmır, sadəcə düzgün çəngəl tələb edir. Go-da interfeyslər məhz belə işləyir: onlar tipdən konkret metodlar tələb edir, qalanı ilə maraqlanmır. Bu dərsdə həm də generikləri öyrənəcəksən — bir funksiyanı müxtəlif tiplər üçün bir dəfə yazmağın yolunu.

İnterfeyslər

İnterfeys metodlar toplusudur: type Shape interface { Area() float64 }. Area() float64 metodu olan istənilən tip avtomatik olaraq Shape sayılır. Java-dakı kimi implements yazmağa ehtiyac yoxdur — uyğunluq gizli (implicit) yoxlanılır. Bu sayədə başqasının yazdığı tip də, müəllifi sənin interfeysindən xəbərsiz olsa belə, ona uyğun gələ bilər.

Go
package main

import (
	"fmt"
	"math"
)

type Shape interface {
	Area() float64
}

type Rect struct {
	W, H float64
}

type Circle struct {
	R float64
}

func (r Rect) Area() float64   { return r.W * r.H }
func (c Circle) Area() float64 { return math.Pi * c.R * c.R }

func main() {
	shapes := []Shape{Rect{W: 3, H: 4}, Circle{R: 1}}
	total := 0.0
	for _, s := range shapes {
		fmt.Printf("%T %.2f\n", s, s.Area())
		total += s.Area()
	}
	fmt.Printf("total %.2f\n", total)
}
Gözlənilən nəticə
main.Rect 12.00
main.Circle 3.14
total 15.14

shapes dilimində həm Rect, həm də Circle saxlanılır, çünki hər ikisi Shape-dir. Dövr hər elementin öz Area metodunu çağırır — bu, polimorfizmdir. %T interfeysin içindəki real tipi göstərir: main.Rect, main.Circle.

Boş interfeys və tip yoxlamaları

Metodsuz interfeysi — interface{} — hər bir tip ödəyir, ona görə belə dəyişən istənilən qiyməti saxlaya bilər. Go 1.18-dən onun qısa adı var: **any**. Amma any içindəki qiymətdən istifadə etmək üçün onun real tipini öyrənmək lazımdır. Tip təsdiqi (type assertion) v.(string) qiyməti çıxarır, tip switch-i isə bir neçə tipi ardıcıl yoxlayır.

Go
package main

import "fmt"

func describe(v any) string {
	switch x := v.(type) {
	case int:
		return fmt.Sprintf("int, doubled: %d", x*2)
	case string:
		return fmt.Sprintf("string of length %d", len(x))
	case nil:
		return "nil value"
	default:
		return fmt.Sprintf("other type: %T", x)
	}
}

func main() {
	for _, v := range []any{21, "Baku", nil, 2.5} {
		fmt.Println(describe(v))
	}
	var box any = "hello"
	s, ok := box.(string)
	n, ok2 := box.(int)
	fmt.Println(s, ok, n, ok2)
}
Gözlənilən nəticə
int, doubled: 42
string of length 4
nil value
other type: float64
hello true 0 false

Tip switch-inin hər qolunda x artıq uyğun tipdədir: case int daxilində onu 2-yə vurmaq, case string daxilində isə len ilə uzunluğunu tapmaq olar. İki qiymətli təsdiq n, ok2 := box.(int) isə uğursuz olanda panic yaratmır: n sıfır qiymət, ok2 isə false alır.

Generiklər

Go 1.18-də dilə generiklər əlavə olundu. Funksiyanın adından sonra kvadrat mötərizədə tip parametri yazılır: func Sum[T Number](nums []T) T. T çağırış zamanı konkret tiplə — int və ya float64 ilə əvəz olunur. Məhdudiyyət (constraint) T-nin hansı tiplər ola biləcəyini göstərir: any — istənilən tip, comparable — == ilə müqayisə oluna bilən tiplər, ~int | ~float64 isə sadalanan tiplər və onlara əsaslanan tiplər.

Go
package main

import "fmt"

type Number interface {
	~int | ~float64
}

func Sum[T Number](nums []T) T {
	var total T
	for _, n := range nums {
		total += n
	}
	return total
}

func Contains[T comparable](items []T, target T) bool {
	for _, it := range items {
		if it == target {
			return true
		}
	}
	return false
}

func main() {
	fmt.Println(Sum([]int{1, 2, 3}))
	fmt.Println(Sum([]float64{1.5, 2.25}))
	fmt.Println(Contains([]string{"go", "java"}, "go"))
	fmt.Println(Contains([]int{1, 2}, 5))
}
Gözlənilən nəticə
6
3.75
true
false
Hər tip üçün surət
func SumInts(nums []int) int {
	total := 0
	for _, n := range nums {
		total += n
	}
	return total
}

func SumFloats(nums []float64) float64 {
	total := 0.0
	for _, n := range nums {
		total += n
	}
	return total
}
Bir generik funksiya
func Sum[T Number](nums []T) T {
	var total T
	for _, n := range nums {
		total += n
	}
	return total
}
Çağırışda tipi yazmağa ehtiyac yoxdur: Sum([]int{1, 2}) yazanda Go T = int olduğunu özü müəyyən edir.

Tiplər də generik ola bilər. Aşağıdakı Stack[T] istənilən tipli elementlər üçün stekdir (son qoyulan birinci götürülür). var zero T sətri T tipinin sıfır qiymətini yaradır: stek boş olanda məhz onu qaytarırıq.

Go
package main

import "fmt"

type Stack[T any] struct {
	items []T
}

func (s *Stack[T]) Push(v T) {
	s.items = append(s.items, v)
}

func (s *Stack[T]) Pop() (T, bool) {
	var zero T
	if len(s.items) == 0 {
		return zero, false
	}
	last := s.items[len(s.items)-1]
	s.items = s.items[:len(s.items)-1]
	return last, true
}

func main() {
	var names Stack[string]
	names.Push("Aysel")
	names.Push("Murad")
	top, _ := names.Pop()
	fmt.Println(top, len(names.items))
	names.Pop()
	v, ok := names.Pop()
	fmt.Printf("%q %v\n", v, ok)
}
Gözlənilən nəticə
Murad 1
"" false

Əsas fikirlər

  • İnterfeys metodlar toplusudur; tip onu implements yazmadan, sadəcə lazımi metodlara malik olduğu üçün ödəyir.
  • Kiçik, bir metodlu interfeyslər (io.Reader, fmt.Stringer) Go-da ən faydalılarıdır.
  • any istənilən qiyməti saxlayır; real tip v, ok := x.(T) və ya tip switch-i ilə öyrənilir.
  • Generiklər (Go 1.18+) tip parametrləri və məhdudiyyətlərlə işləyir: [T any], [T comparable], [T ~int | ~float64].
  • Eyni alqoritm üçün generik, fərqli davranış üçün interfeys seç.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Tip Shape interfeysini necə ödəyir?