- Bir arayüz tanımlamak ve örtük olarak nasıl karşılandığını açıklamak
- Bir
anydeğ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.
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 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.
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
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.
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}) 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.
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
Önemli noktalar
- Arayüz bir metot kümesidir; bir tür onu
implementsyazmadan, yalnızca gerekli metotlara sahip olarak karşılar. - Tek metotlu küçük arayüzler (
io.Reader,fmt.Stringer) Go'da en yararlı olanlardır. anyher 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.
Shape arayüzünü nasıl karşılar?