GoSuda

Go-interfacet er ikke arv.

By Yunjin Lee
views ...

Oversigt

Go-interfaces gør det nemt at have funktioner med de samme argumenter og returværdier i flere structs, men dette adskiller sig fra, hvordan Javas extends-nøgleord udvider og overskriver den interne funktionsadfærd. Selvom man kun kan undgå at forveksle det med arv ved at forstå Gos kompositionelle kodegenbrug korrekt, er det svært at opnå en teoretisk perfekt forståelse fra starten. Lad os undersøge dette med et scenario, der er tilbøjeligt til fejl.

Almindelige fejl

Begyndere kan begå følgende fejl:

 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 kode ser umiddelbart problemfri ud, hvis man fejlagtigt antager, at Go følger traditionel arv. Men dens output er 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 bliver Gos adfærd blot tydelig.

1watermelon := apple

Denne kode konverterer på ingen måde Apple direkte til en Watermelon-klasse.watermelon er derimod blot en pointer til apple.

Det skal igen understreges, at Go ikke følger det traditionelle arvebegreb.

Hvis man skriver kode med denne misforståelse, vil det føre til kritiske fejl såsom meningsløs pointer-generering og uventet kopiering af funktioner til andre structs.

Hvad ville så være et eksemplarisk stykke kode?

Passende eksempel i Go-sprog

 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 dog muligt at få det til at se ud som om der er arv i Go. Et eksempel herpå er anonym embedding. Dette er muligt ved at erklære en intern struct som en unavngiven struct. I sådanne tilfælde er det muligt at få adgang til felterne i den underordnede struct uden eksplicit angivelse. Ved at bruge dette mønster, hvor felterne i den underordnede struct promoveres til den overordnede struct, kan læsbarheden forbedres i visse tilfælde. Det anbefales dog ikke at bruge dette, når den underordnede struct skal vises eksplicit.

 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 eksempel er forskellene som følger:

1w.PrintAll() // Automatisk promovering via den unavngivne struct, ikke w.Friut.PrintAll()

Begge eksempler fremhæver disse vigtige punkter:

  • main skal være minimalistisk, og funktioner skal opdeles efter funktionalitet.
  • Hvis det er forskellige structs, skal det være forskellige objekter.
  • Hvis deling er nødvendig, skal interne structs anvendes.

Hvilke fordele er der ved en sådan programmeringsfilosofi?

Fordele

  • Klar sondring mellem metoder, der skal deles, og dem, der ikke skal.
  • Adskillelse af ansvarsområder for individuelle structs og metoder.
  • Strukturelt adskilt kode i henhold til specificerede funktionskrav.

I starten kan Go-sproget virke uvant, da det adskiller sig fra traditionel OOP, men når man først er vant til det, muliggør det eksplicit programmering.

Resumé

  • Isoler ansvarsområder.
  • Opdel i detaljer på struct-niveau.
  • Metoder bør ikke forstås som Javas abstrakte klasser.
  • Udfør eksplicit og konkret programmering. Go-sproget skal behandles enklere og mere individuelt end traditionelle OOP-modeller. Lad os skrive kode trinvist og strukturelt adskilt i stedet for at programmere ekspansivt.