- İnterfeys elan etmək və onun gizli ödənilməsini izah etmək
anyqiymə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.
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)
}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.
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)
}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.
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))
}6 3.75 true false
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
}func Sum[T Number](nums []T) T {
var total T
for _, n := range nums {
total += n
}
return total
}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.
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)
}Murad 1 "" false
Əsas fikirlər
- İnterfeys metodlar toplusudur; tip onu
implementsyazmadan, 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. anyistənilən qiyməti saxlayır; real tipv, 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.
Shape interfeysini necə ödəyir?