İçeriğe geç
Educora
Orta18 dk7 / 10

Arayüzler ve generic'ler

Örtük olarak karşılanan arayüzler, `any`, tür iddiaları ve tür switch'leriyle çalış; generic fonksiyonlar ve türler yaz.

Kendini test et
Bu derste öğreneceklerin
  • Bir arayüz tanımlamak ve örtük olarak nasıl karşılandığını açıklamak
  • Bir any değerinin gerçek türünü tür iddiası ve tür switch'iyle öğrenmek
  • Tür parametreleri ve kısıtlarla generic fonksiyonlar ve türler yazmak

Aynı prize telefon şarj aletini, su ısıtıcısını ya da bir lambayı takabilirsin: priz cihazın ne olduğuyla ilgilenmez, yalnızca doğru fişi ister. Go'daki arayüzler tam olarak böyle çalışır: bir türden belirli metotlar isterler ve gerisiyle ilgilenmezler. Bu derste ayrıca generic'leri de öğreneceksin: bir fonksiyonu birçok tür için tek seferde yazmanın yolu.

Arayüzler

Bir arayüz, metotlardan oluşan bir kümedir: type Shape interface { Area() float64 }. Area() float64 metoduna sahip her tür otomatik olarak bir Shape sayılır. Java'daki gibi implements yazmaya gerek yoktur; uyum örtük (implicit) olarak denetlenir. Bu sayede başkasının yazdığı bir tür, yazarı senin arayüzünden habersiz olsa bile ona uyabilir.

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)
}
Beklenen çıktı
main.Rect 12.00
main.Circle 3.14
total 15.14

shapes diliminde hem Rect hem de Circle tutulur, çünkü ikisi de birer Shape'tir. Döngü her elemanın kendi Area metodunu çağırır; buna çok biçimlilik denir. %T, arayüzün içindeki gerçek türü gösterir: main.Rect, main.Circle.

Boş arayüz ve tür denetimleri

Metotsuz arayüzü, yani interface{}'i her tür karşılar; bu yüzden bu türden bir değişken herhangi bir değeri tutabilir. Go 1.18'den beri kısa bir adı vardır: **any**. Ama any içindeki değeri kullanmak için onun gerçek türünü öğrenmek gerekir. Tür iddiası (type assertion) v.(string) değeri çıkarır, tür switch'i ise birkaç türü sırayla denetler.

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)
}
Beklenen çıktı
int, doubled: 42
string of length 4
nil value
other type: float64
hello true 0 false

Tür switch'inin her kolunda x zaten uygun türdedir: case int içinde onu 2 ile çarpabilir, case string içinde len ile uzunluğunu bulabilirsin. İki değerli iddia n, ok2 := box.(int) başarısız olduğunda panic'e yol açmaz: n sıfır değerini, ok2 ise false değerini alır.

Generic'ler

Go 1.18 ile dile generic'ler eklendi. Fonksiyon adından sonra köşeli parantez içinde bir tür parametresi yazılır: func Sum[T Number](nums []T) T. Çağrı sırasında T, int ya da float64 gibi somut bir türle değiştirilir. Kısıt (constraint), T'nin hangi türler olabileceğini belirtir: any her türü, comparable == ile karşılaştırılabilen türleri, ~int | ~float64 ise sayılan türleri ve onlara dayanan türleri ifade eder.

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))
}
Beklenen çıktı
6
3.75
true
false
Her tür için bir kopya
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
}
Tek bir generic fonksiyon
func Sum[T Number](nums []T) T {
	var total T
	for _, n := range nums {
		total += n
	}
	return total
}
Çağrıda türü yazmak gerekmez: Sum([]int{1, 2}) için Go T = int olduğunu kendisi çıkarır.

Türler de generic olabilir. Aşağıdaki Stack[T], her türden eleman için bir yığındır (son giren ilk çıkar). var zero T satırı T türünün sıfır değerini oluşturur: yığın boşken döndürdüğümüz tam olarak odur.

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)
}
Beklenen çıktı
Murad 1
"" false

Önemli noktalar

  • Arayüz bir metot kümesidir; bir tür onu implements yazmadan, yalnızca gerekli metotlara sahip olarak karşılar.
  • Tek metotlu küçük arayüzler (io.Reader, fmt.Stringer) Go'da en yararlı olanlardır.
  • any her değeri tutar; gerçek türü v, ok := x.(T) ya da bir tür switch'iyle öğrenilir.
  • Generic'ler (Go 1.18+) tür parametreleri ve kısıtlar kullanır: [T any], [T comparable], [T ~int | ~float64].
  • Farklı türler üzerinde aynı algoritma için generic, farklı davranış için arayüz seç.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Bir tür Shape arayüzünü nasıl karşılar?