Go-grensesnitt er ikke arv
Oversikt
Go-grensesnitt muliggjør enkelt at flere strukturer har funksjoner med de samme argumentene og returverdiene, men det er annerledes enn hvordan Javas extends-nøkkelord utvider og overstyrer oppførselen til interne funksjoner på en passende måte. For ikke å forveksle det med arv, må man forstå Go's komposisjonelle kodegjenbruk riktig, men en teoretisk fullstendig forståelse fra starten er vanskelig. La oss utforske dette med et scenario som er lett å feile i.
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 virker 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.
Snarere er watermelon bare en peker til apple.
Her understrekes det igjen: Go følger ikke det tradisjonelle arvekonseptet.
Hvis man skriver kode med denne misforståelsen, kan det oppstå fatale feil som meningsløs pekergenerering og uventet funksjonskopiering for andre strukturer.
Hva er da eksempelet på god kode?
Passende eksempel 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}
Det er imidlertid mulig å få det til å se ut som arv i Go. Dette er et eksempel på anonym embedding. 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 underordnede strukturen uten å spesifisere dem. Ved å bruke dette mønsteret, der feltene i den underordnede strukturen promoveres til den overordnede strukturen, kan lesbarheten forbedres i noen tilfeller. Det anbefales imidlertid å unngå å bruke dette når den underordnede strukturen bør 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() // Kalles via automatisk promovering gjennom den navnløse strukturen, ikke w.Fruit.PrintAll()
Begge eksemplene har disse viktige punktene:
mainer forenklet, funksjoner er delt etter funksjonalitet.- Hvis det er forskjellige strukturer, bruk forskjellige objekter.
- Hvis deling er nødvendig, bruk interne strukturer.
Hvilke fordeler har en slik programmeringsfilosofi?
Fordeler
- Klar skille mellom metoder som trenger å deles og de som ikke gjør det.
- Ansvaret er delt mellom individuelle strukturer og metoder.
- Koden er strukturelt delt i henhold til nødvendige funksjonsspesifikasjoner.
Til å begynne med kan Go-språket virke uvant fordi det er annerledes enn tradisjonell OOP, men når man blir vant til det, muliggjør det eksplisitt programmering.
Sammendrag
- Isoler ansvaret.
- Del opp i detaljerte strukturenheter.
- Metoder skal ikke forstås som Javas abstrakte klasser.
- Utfør eksplisitt og spesifikk programmering. Go-språket bør behandles enklere og mer individuelt enn den tradisjonelle OOP-modellen. La oss skrive kode på en trinnvis og strukturelt delt måte, i stedet for å programmere ekspansivt.