GoSuda

Go Arayüzleri Kalıtım Değildir

By Yunjin Lee
views ...

Ö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:

  • main fonksiyonunu 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.