Go-gränssnitt är inte arv
Översikt
Go-gränssnitt möjliggör enkelt att flera strukturer kan ha funktioner med samma argument och returvärden, men det skiljer sig från hur Javas extends
-nyckelord utökar och åsidosätter beteendet hos de interna funktionerna. Man bör korrekt förstå Gos kompositionella kodåteranvändning för att undvika förväxling med arv, men en teoretiskt perfekt förståelse från början är svår. Låt oss undersöka detta med scenarier där misstag lätt uppstår.
Vanliga misstag
Nybörjare kan göra följande misstag:
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}
Denna kod verkar inte ha några problem om man felaktigt antar att Go följer traditionellt arv. Dock är utdata följande:
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
Här blir Gos beteende tydligt.
1watermelon := apple
Denna kod konverterar inte alls Apple
direkt till klassen Watermelon
.watermelon
är endast en pekare till apple
.
Här betonar vi återigen att Go inte följer det traditionella arvsbegreppet.
Om man skriver kod med denna missuppfattning kan det leda till kritiska fel som meningslös pekargenerering och oväntad kopiering av funktioner för andra strukturer.
Vad skulle då vara ett föredömligt kodexempel?
Lämpligt exempel 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}
Dock är det möjligt att få Go att likna arv. Detta är ett exempel på anonym inbäddning. Detta är möjligt genom att deklarera en intern struktur som en namnlös struktur. I sådana fall är det möjligt att få åtkomst till fälten i den underordnade strukturen utan att explicit nämna dem. Genom att använda detta mönster för att promovera fälten i en underordnad struktur till en överordnad struktur kan läsbarheten förbättras i vissa fall. Det rekommenderas dock att inte använda detta när den underordnade strukturen behöver visas explicit.
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 detta exempel finns följande skillnader:
1w.PrintAll() // w.Friut.PrintAll() anropas inte, utan ett automatiskt promota-anrop via en namnlös struktur.
2De viktiga punkterna i båda exemplen är följande:
3- Håll main enkel, och funktioner separerade efter funktionalitet.
4- Använd olika objekt för olika strukturer.
5- Använd interna strukturer när delning är nödvändigt.
6
7Vilka fördelar har denna programmeringsfilosofi?
8
9## Fördelar
10
11- Tydlig åtskillnad mellan metoder som behöver delas och de som inte behöver det.
12- Ansvarsfördelning för individuella strukturer och metoder.
13- Strukturellt åtskild kod baserad på nödvändiga funktionsspecifikationer.
14
15Till en början kan Go-språket kännas ovant eftersom det skiljer sig från traditionell OOP, men när man väl har vant sig kan man uppnå explicit programmering.
16
17## Sammanfattning
18- Isolera ansvarsområden.
19- Dela upp i detaljerade enheter per struktur.
20- Metoder ska inte förstås som abstrakta klasser i Java.
21- Programmera explicit och konkret.
22Go-språket är enklare och ska behandlas mer individuellt än den traditionella OOP-modellen. Låt oss skriva kod stegvis och strukturellt åtskilt snarare än extensivt.
23