Go Metodlar
Metodlar ile fonksiyonlar arasındaki fark, metodların alıcısı (receiver) vardır, fonksiyonların yoktur ve sadece özel türler metoda sahip olabilir. Önce bir örneğe bakalım.
type IntSlice []int
func (i IntSlice) Get(index int) int {
return i[index]
}
func (i IntSlice) Set(index, val int) {
i[index] = val
}
func (i IntSlice) Len() int {
return len(i)
}Önce IntSlice adında bir tür tanımladık, alt türü []int, ardından üç metod tanımladık: Get, Set ve Len. Metodların görünümü fonksiyonlardan çok farklı değildir, sadece (i IntSlice) bölümü eklenmiştir. i alıcıdır, IntSlice alıcının türüdür. Alıcı diğer dillerdeki this veya self gibidir, ancak Go'da açıkça belirtmeniz gerekir.
func main() {
var intSlice IntSlice
intSlice = []int{1, 2, 3, 4, 5}
fmt.Println(intSlice.Get(0))
intSlice.Set(0, 2)
fmt.Println(intSlice)
fmt.Println(intSlice.Len())
}Metod kullanımı bir sınıfın üye metodunu çağırmaya benzer, önce tanımla, sonra başlat, sonra çağır.
Değer Alıcısı
Alıcılar iki türe ayrılır: değer alıcısı ve pointer alıcısı. Önce bir örneğe bakalım:
type MyInt int
func (i MyInt) Set(val int) {
i = MyInt(val) // değiştirildi, ancak hiçbir etkisi yok
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}Yukarıdaki kod çalıştırıldıktan sonra, myInt değerinin hala 1 olduğu, 2 olarak değiştirilmediği fark edilir. Metod çağrıldığında, alıcının değeri metoda aktarılır. Yukarıdaki örnekte alıcı bir değer alıcısıdır, basitçe bir formal parametre olarak görülebilir. Bir formal parametrenin değerini değiştirmek, metod dışındaki değeri etkilemez. Peki pointer ile çağrılırsa ne olur?
func main() {
myInt := MyInt(1)
(&myInt).Set(2)
fmt.Println(myInt)
}Maalesef, bu tür kodlar hala dahili değeri değiştiremez. Go, alıcı türünü eşleştirebilmek için onu referanssız hale getirir, (*(&myInt)).Set(2) olarak yorumlar.
Pointer Alıcısı
Biraz değiştirerek, myInt değerini normal şekilde değiştirebiliriz.
type MyInt int
func (i *MyInt) Set(val int) {
*i = MyInt(val)
}
func main() {
myInt := MyInt(1)
myInt.Set(2)
fmt.Println(myInt)
}Şimdi alıcı bir pointer alıcısıdır. myInt bir değer türü olmasına rağmen, değer türü üzerinden pointer alıcısının metodunu çağırdığınızda, Go bunu (&myint).Set(2) olarak yorumlar. Bu yüzden metodun alıcısı pointer olduğunda, çağırıcı pointer olmasa bile, dahili değer değiştirilebilir.
Fonksiyon parametre aktarımı sırasında değer kopyalaması yapılır. Eğer bir tamsayı aktarılıyorsa, o tamsayı kopyalanır. Eğer bir slice ise, o slice kopyalanır. Ancak bir pointer ise, sadece pointer kopyalanır. Açıkçası, bir pointer aktarmak bir slice aktarmaktan daha az kaynak tüketir. Alıcılar da aynı mantıktadır, değer alıcısı ve pointer alıcısı da aynı prensibe tabidir. Çoğu durumda, pointer alıcısı kullanmanız önerilir, ancak ikisi karıştırılmamalıdır. Ya hep kullanın ya da hiç kullanmayın. Aşağıdaki örneğe bakalım.
TIP
Önce arayüz hakkında bilgi edinmeniz gerekir.
type Animal interface {
Run()
}
type Dog struct {
}
func (d *Dog) Run() {
fmt.Println("Run")
}
func main() {
var an Animal
an = Dog{}
// an = &Dog{} doğru yöntem
an.Run()
}Bu kod derlenemez, derleyici aşağıdaki hatayı çıktılayacaktır:
cannot use Dog{} (value of type Dog) as type Animal in assignment:
Dog does not implement Animal (Run method has pointer receiver)Türkçesi: Animal türü değişkeni başlatmak için Dog{} kullanılamaz, çünkü Dog, Animal'ı uygulamamıştır (Run metodu pointer alıcısına sahiptir). Çözümün iki yolu vardır: birincisi pointer alıcısını değer alıcısına değiştirmek, ikincisi Dog{}'i &Dog{} olarak değiştirmektir. Şimdi sırayla açıklayalım.
type Dog struct {
}
func (d Dog) Run() { // değer alıcısına değiştirildi
fmt.Println("Run")
}
func main() { // normal çalışır
var an Animal
an = Dog{}
// an = &Dog{} aynı şekilde çalışır
an.Run()
}Orijinal kodda, Run metodunun alıcısı *Dog idi, doğal olarak Animal arayüzünü uygulayan Dog pointer'ıdır, Dog struct'ı değil. Bunlar iki farklı türdür, bu yüzden derleyici Dog{}'in Animal uygulaması olmadığını düşünür, bu yüzden an değişkenine atanamaz. Bu yüzden ikinci çözüm an değişkenine Dog pointer'ı atamaktır. Ancak değer alıcısı kullanıldığında, Dog pointer'ı hala normal şekilde animal'a atanabilir. Bunun nedeni Go'nun uygun durumlarda pointer'ı referanssız hale getirmesidir. Pointer üzerinden Dog struct'ı bulunabilir, ancak tersi durumda Dog struct'ı üzerinden Dog pointer'ı bulunamaz. Eğer sadece struct içinde değer alıcısı ve pointer alıcısı karıştırılırsa sorun olmaz, ancak arayüz ile birlikte kullanıldıktan sonra hata oluşur. Her zaman ya değer alıcısı ya da pointer alıcısı kullanarak iyi bir standart oluşturmak, sonraki bakım yükünü de azaltır.
Bir diğer durum da, değer alıcısı adreslenebilir olduğunda, Go otomatik olarak pointer operatörünü ekleyerek çağrı yapar. Örneğin slice adreslenebilirdir, hala değer alıcısı üzerinden dahili değerini değiştirebilirsiniz. Aşağıdaki kod gibi:
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func main() {
s := make(Slice, 1)
s.Set(0, 1)
fmt.Println(s)
}Çıktı:
[1]Ancak bu başka bir soruna neden olur: eğer öğe eklerseniz, durum farklı olur. Aşağıdaki örneğe bakalım:
type Slice []int
func (s Slice) Set(i int, v int) {
s[i] = v
}
func (s Slice) Append(a int) {
s = append(s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}[1]Çıktı hala önceki gibi, append fonksiyonunun dönüş değeri vardır. Slice'a öğe eklendikten sonra orijinal slice'ın üzerine yazılmalıdır, özellikle kapasite genişletildikten sonra. Metod içinde değer alıcısını değiştirmek hiçbir etki yapmaz, bu da örnekteki sonuca neden olur. Pointer alıcısına değiştirirseniz normal olur.
type Slice []int
func (s *Slice) Set(i int, v int) {
(*s)[i] = v
}
func (s *Slice) Append(a int) {
*s = append(*s, a)
}
func main() {
s := make(Slice, 1, 2)
s.Set(0, 1)
s.Append(2)
fmt.Println(s)
}Çıktı:
[1 2]