GoSuda

Antarmuka Go bukanlah pewarisan

By Yunjin Lee
views ...

Gambaran Umum

Interface Go memudahkan beberapa struct untuk memiliki fungsi dengan argumen dan nilai kembalian yang sama, tetapi berbeda dari cara keyword extends pada Java yang memperluas dan mengesampingkan fungsi internal secara tepat. Meskipun pemahaman yang benar tentang penggunaan kembali kode secara komposisi di Go akan mencegah kebingungan dengan inheritance, pemahaman teoritis yang sempurna sejak awal itu sulit. Mari kita bahas dengan skenario yang mudah salah.

Kesalahan Umum

Para 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 tampak tidak bermasalah jika Anda salah mengira bahwa Go mengikuti inheritance tradisional. Namun, hasil output-nya 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.

Sekali lagi ditekankan di sini, Go tidak mengikuti konsep inheritance tradisional.

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

Lalu, bagaimana dengan kode yang patut dicontoh?

Contoh yang Tepat 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, di Go, dimungkinkan untuk membuatnya terlihat seperti inheritance. Contohnya adalah embedding anonim. Ini dimungkinkan dengan mendeklarasikan struct internal sebagai struct tanpa nama. Dalam kasus ini, field dari struct anak dapat diakses begitu saja tanpa perlu disebutkan secara eksplisit. Pola promosi field dari struct anak ke struct induk ini dapat meningkatkan keterbacaan dalam beberapa kasus. Namun, disarankan untuk tidak menggunakannya jika struct anak 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, ada perbedaan-perbedaan berikut:

 1w.PrintAll() // w.Friut.PrintAll() bukan, pemanggilan promosi otomatis melalui struct tanpa nama
 2Dua contoh ini memiliki poin-poin penting berikut:
 3- main harus ringkas, fungsi harus berdasarkan fitur
 4- jika struct berbeda, gunakan objek yang berbeda
 5- jika diperlukan pembagian, gunakan struct internal
 6
 7Apa keuntungan dari filosofi pemrograman seperti ini?
 8
 9## Keuntungan
10
11- Perbedaan yang jelas antara metode yang memerlukan pembagian dan yang tidak
12- Pemisahan tanggung jawab pada struct dan metode individual
13- Kode yang terstruktur secara terpisah sesuai dengan spesifikasi fungsi yang dibutuhkan
14
15Pada awalnya, Go mungkin terasa tidak asing karena berbeda dari OOP tradisional, tetapi setelah terbiasa, pemrograman yang eksplisit dapat dilakukan.
16
17## Ringkasan
18- Isolasi tanggung jawab
19- Bagi secara detail dalam unit struct
20- Metode tidak boleh dipahami seperti abstract class di Java
21- Lakukan pemrograman yang eksplisit dan spesifik
22Bahasa Go harus diperlakukan secara lebih sederhana dan individual dibandingkan model OOP tradisional. Daripada memprogram secara ekstensif, mari kita tulis secara bertahap dan terstruktur secara terpisah.
23