GoSuda

Go interface bukanlah inheritance.

By Yunjin Lee
views ...

Gambaran Umum

Antarmuka Go memungkinkan beberapa struct memiliki fungsi dengan argumen dan nilai pengembalian yang sama dengan mudah, tetapi ini berbeda dari cara kata kunci extends pada Java yang secara tepat memperluas dan menimpa perilaku fungsi internal. Meskipun pemahaman yang tepat tentang penggunaan kembali kode komposit Go akan mencegah kebingungan dengan inheritance, mencapai pemahaman teoritis yang sempurna sejak awal adalah sulit. Mari kita pelajari dengan skenario di mana kesalahan sering terjadi.

Kesalahan Umum

Pemula mungkin melakukan kesalahan berikut:

 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
63	apple.SetLabel("Apple_1")
64	fmt.Println("Apple, before copied to Watermelon")
65	apple.PrintAll()
66	watermelon.SetLabel("WaterMelon_2")
67	fmt.Println("Apple, after copied to Watermelon")
68	apple.PrintAll()
69	fmt.Println("Watermelon, which inherited Apple's Method")
70	watermelon.PrintAll()
71}

Kode ini mungkin terlihat tidak bermasalah jika kita keliru berasumsi bahwa Go mengikuti inheritance tradisional. Namun, hasil keluarannya adalah sebagai berikut:

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

Di sini, perilaku Go menjadi jelas.

1watermelon := apple

Kode ini sama sekali tidak mengubah Apple menjadi kelas Watermelon. Sebaliknya, watermelon hanyalah sebuah pointer ke apple.

Di sini, ditekankan lagi bahwa Go tidak mengikuti konsep inheritance tradisional.

Jika kode ditulis dengan kesalahpahaman seperti ini, akan terjadi kesalahan fatal seperti pembuatan pointer yang tidak berarti, dan penyalinan fungsi yang tidak terduga untuk struct lain.

Lalu, bagaimana dengan kode yang baik?

Contoh yang Sesuai dalam Bahasa Go

 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 {
16	Name  string
17	Brix  float64
18}
19
20type Apple struct {
21	Label string
22	Fruit BaseFruit
23}
24
25type Watermelon struct {
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}

Namun, dalam Go juga dimungkinkan untuk membuatnya terlihat seperti inheritance. Contohnya adalah anonymous embedding. Ini dimungkinkan dengan mendeklarasikan struct internal sebagai struct tanpa nama. Dalam kasus seperti ini, field dari sub-struct dapat diakses langsung tanpa perlu disebutkan secara eksplisit. Dengan menggunakan pola promosi field sub-struct ke super-struct, keterbacaan dapat ditingkatkan dalam beberapa kasus. Namun, disarankan untuk tidak menggunakannya jika sub-struct perlu ditampilkan secara eksplisit.

 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 {
16	Name  string
17	Brix  float64
18}
19
20type Apple struct {
21	Label string
22	BaseFruit
23}
24
25type Watermelon struct {
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;
42	a.Name 	= "apple";
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()
53}
54
55func (w *Watermelon) SetLabel(lbl string) {
56	w.Brix = 10;
57	w.Name = "Watermelon";
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()
66}
67
68func main() {
69	apple := new(Apple)
70	watermelon := new(Watermelon)
71	apple.SetLabel("Apple_1")
72	watermelon.SetLabel("WaterMelon_2")
73}

Dalam contoh ini terdapat perbedaan berikut:

1w.PrintAll() // w.Friut.PrintAll() bukan pemanggilan, melainkan pemanggilan promosi otomatis melalui struct tanpa nama

Kedua contoh tersebut memiliki poin-poin penting sebagai berikut:

  • main harus singkat, fungsi harus berdasarkan fungsionalitas
  • jika struct berbeda, gunakan objek yang berbeda
  • jika diperlukan berbagi, gunakan struct internal

Apa keuntungan dari filosofi pemrograman seperti ini?

Keuntungan

  • Perbedaan yang jelas antara metode yang perlu dibagikan dan yang tidak
  • Pemisahan tanggung jawab untuk struct dan metode individual
  • Kode yang terstruktur secara terpisah sesuai dengan spesifikasi fungsionalitas yang diperlukan

Pada awalnya, bahasa Go mungkin terasa asing karena berbeda dari OOP tradisional, namun setelah terbiasa, pemrograman eksplisit menjadi mungkin.

Ringkasan

  • Isolasikan tanggung jawab
  • Bagilah secara terperinci berdasarkan unit struct
  • Metode tidak boleh dipahami seperti kelas abstrak Java
  • Lakukan pemrograman yang eksplisit dan konkret Bahasa Go harus ditangani dengan cara yang lebih sederhana dan individual daripada model OOP tradisional. Daripada memprogram secara ekstensif, mari kita menulis kode secara bertahap dan terstruktur.