Go Arayüzleri Kalıtım Değildir
Özet
Go arayüzleri, aynı argümanlara ve dönüş değerlerine sahip fonksiyonların birden fazla struct'ta kolayca bulunmasına olanak tanır, ancak Java'daki extends anahtar kelimesi gibi, dahili fonksiyonların davranışını uygun şekilde genişletme ve geçersiz kılma yönteminden farklıdır. Go'nun kompozisyonel kod yeniden kullanımını doğru bir şekilde anlamak, miras ile karıştırılmamasını sağlayacaktır, ancak başlangıçta teorik olarak mükemmel bir anlayışa ulaşmak zordur. Hatalara yol açabilecek senaryolarla birlikte bunu inceleyelim.
Sık Yapılan Hatalar
Yeni başlayanlar aşağıdaki hataları yapabilirler:
1package main
2import (
3 "fmt"
4 "strings"
5)
6
7type Fruits interface {
8 GetBrix() float64
9 GetName() string
10 SetLabel()
11 GetLabel(string) string
12 PrintAll()
13}
14
15type Apple struct {
16 Label string
17 Name string
18 Brix float64
19}
20
21type Watermelon struct {
22 Label string
23 Name string
24 Brix float64
25}
26
27func (a *Apple) PrintAll() {
28 fmt.Printf("Fruit: %s, Label: %s, Brix: %v\n", a.Name, a.Label, a.Brix)
29}
30
31const (
32 NO_LABEL = "EMPTY LABEL"
33)
34
35func (a *Apple) SetLabel(lbl string) {
36 a.Brix = 14.5;
37 a.Name = "apple";
38 lbl_lower := strings.ToLower(lbl)
39 if strings.Contains(lbl_lower, a.Name) {
40 fmt.Println("Succeed: Label was ", lbl)
41 a.Label = lbl;
42 } else {
43 fmt.Println("Failed: Label was ", lbl)
44 a.Label = NO_LABEL;
45 }
46}
47
48func (w *Watermelon) SetLabel(lbl string) {
49 w.Brix = 10;
50 w.Name = "watermelon";
51 lbl_lower := strings.ToLower(lbl)
52 if strings.Contains(lbl_lower, w.Name) {
53 w.Label = lbl;
54 } else {
55 w.Label = NO_LABEL;
56 }
57}
58
59func main() {
60 fmt.Println("Inheritance test #1")
61 apple := new(Apple)
62 watermelon := apple // Bu satırda mantıksal bir hata var. Apple'ı Watermelon'a kopyalamak yerine, watermelon'ı apple'ın bir pointer'ı olarak atıyor.
63 apple.SetLabel("Apple_1")
64 fmt.Println("Apple, before copied to Watermelon")
65 apple.PrintAll()
66 watermelon.SetLabel("WaterMelon_2") // watermelon'ın SetLabel'ını çağırmak aslında apple'ın SetLabel'ını çağırır.
67 fmt.Println("Apple, after copied to Watermelon")
68 apple.PrintAll()
69 fmt.Println("Watermelon, which inherited Apple's Method")
70 watermelon.PrintAll()
71}
Bu tür bir kod, Go'nun geleneksel mirası takip ettiğini yanlış varsayarsak sorunsuz görünebilir. Ancak, çıktısı aşağıdaki gibidir:
1Inheritance test #1
2Succeed: Label was Apple_1
3Apple, before copied to Watermelon
4Fruit: apple, Label: Apple_1, Brix: 14.5
5Failed: Label was WaterMelon_2
6Apple, after copied to Watermelon
7Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
8Watermelon, which inherited Apple's Method
9Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
Burada Go'nun davranışı gayet netleşmektedir.
1watermelon := apple
Bu kod, Apple'ı Watermelon sınıfına dönüştürmez.
Ancak watermelon, apple'a ait bir işaretçidir.
Burada tekrar vurgulamak gerekir ki, Go geleneksel miras kavramını takip etmez.
Bu tür bir yanlış anlaşılma ile kod yazılırsa, anlamsız işaretçi oluşturma, beklenmedik şekilde başka struct'lar için fonksiyon kopyalama gibi ciddi hatalar ortaya çıkabilir.
Peki, örnek teşkil eden bir kod nasıl olmalıdır?
Go Dilinde Uygun Örnek
1package main
2import (
3 "fmt"
4 "strings"
5)
6
7type Fruits interface {
8 GetBrix() float64
9 GetName() string
10 SetLabel()
11 GetLabel(string) string
12 PrintAll()
13}
14
15type BaseFruit struct { // Temel meyve özelliklerini içeren struct
16 Name string
17 Brix float64
18}
19
20type Apple struct { // Apple struct'ı, BaseFruit'i içermektedir
21 Label string
22 Fruit BaseFruit
23}
24
25type Watermelon struct { // Watermelon struct'ı, BaseFruit'i içermektedir
26 Label string
27 Fruit BaseFruit
28
29}
30
31func (b *BaseFruit) PrintAll() {
32 fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
33}
34
35
36const (
37 NO_LABEL = "EMPTY LABEL"
38)
39
40func (a *Apple) SetLabel(lbl string) {
41 a.Fruit.Brix = 14.5;
42 a.Fruit.Name = "apple";
43 lbl_lower := strings.ToLower(lbl)
44 if strings.Contains(lbl_lower, a.Fruit.Name) {
45 fmt.Println("Succeed: Label was ", lbl)
46 a.Label = lbl;
47 } else {
48 fmt.Println("Failed: Label was ", lbl)
49 a.Label = NO_LABEL;
50 }
51 fmt.Printf("Fruit %s label set to %s\n", a.Fruit.Name, a.Label);
52 a.Fruit.PrintAll()
53}
54
55func (w *Watermelon) SetLabel(lbl string) {
56 w.Fruit.Brix = 10;
57 w.Fruit.Name = "Watermelon";
58 lbl_lower := strings.ToLower(lbl)
59 if strings.Contains(lbl_lower, w.Fruit.Name) {
60 w.Label = lbl;
61 } else {
62 w.Label = NO_LABEL;
63 }
64 fmt.Printf("Fruit %s label set to %s\n", w.Fruit.Name, w.Label);
65 w.Fruit.PrintAll()
66}
67
68func main() {
69 apple := new(Apple)
70 watermelon := new(Watermelon)
71 apple.SetLabel("Apple_1")
72 watermelon.SetLabel("WaterMelon_2")
73}
Ancak, Go'da miras gibi görünen bir yapı oluşturmak mümkündür. Bu, anonim gömme olarak bilinen bir örnektir. Bu, dahili bir struct'ı isimsiz bir struct olarak bildirmekle mümkündür. Bu durumda, alt struct'ın alanlarını açıkça belirtmeden kullanmak yine de erişilebilirliği sağlar. Alt struct'ın alanlarını üst struct'a yükselten bu desenin kullanılması, duruma göre okunabilirliği artırabilir. Ancak, alt struct'ın açıkça gösterilmesi gereken durumlarda kullanılmaması tavsiye edilir.
1package main
2import (
3 "fmt"
4 "strings"
5)
6
7type Fruits interface {
8 GetBrix() float64
9 GetName() string
10 SetLabel()
11 GetLabel(string) string
12 PrintAll()
13}
14
15type BaseFruit struct { // Temel meyve özelliklerini içeren struct
16 Name string
17 Brix float64
18}
19
20type Apple struct { // Apple struct'ı, BaseFruit'i anonim olarak gömmektedir
21 Label string
22 BaseFruit
23}
24
25type Watermelon struct { // Watermelon struct'ı, BaseFruit'i anonim olarak gömmektedir
26 Label string
27 BaseFruit
28
29}
30
31func (b *BaseFruit) PrintAll() {
32 fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
33}
34
35
36const (
37 NO_LABEL = "EMPTY LABEL"
38)
39
40func (a *Apple) SetLabel(lbl string) {
41 a.Brix = 14.5; // BaseFruit'in alanlarına doğrudan erişim
42 a.Name = "apple"; // BaseFruit'in alanlarına doğrudan erişim
43 lbl_lower := strings.ToLower(lbl)
44 if strings.Contains(lbl_lower, a.Name) {
45 fmt.Println("Succeed: Label was ", lbl)
46 a.Label = lbl;
47 } else {
48 fmt.Println("Failed: Label was ", lbl)
49 a.Label = NO_LABEL;
50 }
51 fmt.Printf("Fruit %s label set to %s\n", a.Name, a.Label);
52 a.PrintAll() // BaseFruit'in PrintAll metoduna doğrudan erişim
53}
54
55func (w *Watermelon) SetLabel(lbl string) {
56 w.Brix = 10; // BaseFruit'in alanlarına doğrudan erişim
57 w.Name = "Watermelon"; // BaseFruit'in alanlarına doğrudan erişim
58 lbl_lower := strings.ToLower(lbl)
59 if strings.Contains(lbl_lower, w.Name) {
60 w.Label = lbl;
61 } else {
62 w.Label = NO_LABEL;
63 }
64 fmt.Printf("Fruit %s label set to %s\n", w.Name, w.Label);
65 w.PrintAll() // BaseFruit'in PrintAll metoduna doğrudan erişim
66}
67
68func main() {
69 apple := new(Apple)
70 watermelon := new(Watermelon)
71 apple.SetLabel("Apple_1")
72 watermelon.SetLabel("WaterMelon_2")
73}
Bu örnekte şu farklılıklar bulunmaktadır:
1w.PrintAll() // w.Fruit.PrintAll() yerine, isimsiz struct aracılığıyla otomatik yükseltme çağrısı
Her iki örnekteki önemli noktalar şunlardır:
mainfonksiyonunu basitleştirin, fonksiyonları işlevlerine göre ayırın.- Farklı struct'lar için farklı nesneler kullanın.
- Paylaşım gerektiğinde dahili struct'ları kullanın.
Bu programlama felsefesinin ne gibi avantajları vardır?
Avantajlar
- Paylaşılması gereken metotlar ile gerekmeyenlerin net bir şekilde ayrılması.
- Bireysel struct'lar ve metotlar için sorumlulukların ayrılması.
- Gerekli fonksiyonel spesifikasyonlara göre yapısal olarak ayrılmış kod.
Başlangıçta Go dili geleneksel OOP'den farklı olduğu için alışılmadık gelebilir, ancak alışıldığında daha açık bir programlama mümkün hale gelir.
Özet
- Sorumlulukları izole edelim.
- Struct'ları ayrıntılı birimlere ayıralım.
- Metotları Java'daki abstract class'lar gibi anlamamalıyız.
- Açık ve somut programlama yapalım. Go dili, geleneksel OOP modelinden daha basit, daha anlaşılır ve bireysel olarak ele alınması gereken bir dildir. Genişletilebilir programlama yapmak yerine, kademeli ve yapısal olarak ayrılmış bir şekilde kod yazalım.