GoSuda

Go-grensesnitt er ikke arv

By Yunjin Lee
views ...

Oversikt

Go-grensesnitt muliggjør enkelt bruk av funksjoner med identiske argumenter og returverdier i flere strukturer, men dette skiller seg fra Java's extends-nøkkelord, som på passende måte utvider og overstyrer den interne funksjonaliteten til disse funksjonene. En korrekt forståelse av Go's komposisjonelle gjenbruk av kode er nødvendig for å unngå forveksling med arv, selv om en teoretisk perfekt forståelse fra starten kan være utfordrende. La oss utforske dette med et scenario som ofte fører til feil.

Vanlige feil

Nybegynnere kan gjøre følgende feil:

 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}

Denne koden kan virke problemfri hvis man feilaktig antar at Go følger tradisjonell arv. Imidlertid er utdataene som følger:

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

Her blir Go's oppførsel tydelig.

1watermelon := apple

Denne koden konverterer ikke Apple direkte til en Watermelon-klasse.watermelon er kun en peker til apple.

Det understrekes igjen: Go følger ikke tradisjonelle arvekonsepter.

Hvis kode skrives med denne misforståelsen, kan det føre til fatale feil som meningsløse pekergenereringer og uventet kopiering av funksjoner for andre strukturer.

Hva er da et eksemplarisk kodeeksempel?

Passende eksempler i Go-språket

 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}

Imidlertid er det mulig å få det til å se ut som arv i Go. Dette er et eksempel på anonym innkapsling. Dette er mulig ved å deklarere den interne strukturen som en navnløs struktur. I slike tilfeller er det mulig å få tilgang til feltene i den underliggende strukturen direkte, uten eksplisitt å spesifisere dem. Ved å bruke dette mønsteret, der feltene i den underliggende strukturen blir promotert til den overordnede strukturen, kan lesbarheten forbedres i visse tilfeller. Imidlertid anbefales det å ikke bruke dette når den underliggende strukturen må vises eksplisitt.

 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}

I dette eksemplet er det disse forskjellene:

 1w.PrintAll() // Automatisk promoteringskall via navnløs struktur, ikke w.Fruit.PrintAll()
 2De viktige punktene i begge eksemplene er som følger:
 3- Forenkle main, funksjoner etter funksjonalitet
 4- Bruk forskjellige objekter for forskjellige strukturer
 5- Bruk interne strukturer når deling er nødvendig
 6
 7Hvilke fordeler har en slik programmeringsfilosofi?
 8
 9## Fordeler
10
11- Tydelig skille mellom metoder som trenger deling og de som ikke gjør det
12- Fordeling av ansvar til individuelle strukturer og metoder
13- Strukturelt separert kode i henhold til nødvendige funksjonsspesifikasjoner
14
15
16Go-språket kan i begynnelsen virke uvant da det skiller seg fra tradisjonell OOP, men med erfaring muliggjør det eksplisitt programmering.
17
18## Oppsummering
19- Isoler ansvarsområder
20- Del opp i detaljerte strukturenheter
21- Metoder bør ikke forstås som abstrakte klasser i Java
22- Programmer eksplisitt og konkret
23Go-språket bør behandles på en enklere og mer individuell måte enn den tradisjonelle OOP-modellen. La oss skrive kode som er trinnvis og strukturelt separert, i stedet for å programmere for utvidbarhet.
24