GoSuda

Go-gränssnitt är inte arv

By Yunjin Lee
views ...

Översikt

Go-gränssnitt gör det enkelt att ha funktioner med samma argument och returvärden i flera strukturer, men det skiljer sig från hur Java:s extends-nyckelord förlänger och åsidosätter beteendet hos interna funktioner. Även om en korrekt förståelse av Go:s kompositionella kodåteranvändning skulle förhindra förvirring med arv, är det svårt att uppnå en teoretiskt perfekt förståelse från början. Låt oss undersöka detta med scenarier som är lätta att missta sig på.

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 vara utan problem om man felaktigt antar att Go följer traditionell arv. Utdata från denna kod är dock 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 Go:s beteende tydligt.

1watermelon := apple

Denna kod konverterar inte Apple direkt till en Watermelon-klass. Istället är watermelon endast en pekare till apple.

Här vill vi återigen betona att Go inte följer det traditionella konceptet av arv.

Om man skriver kod med denna missuppfattning kan det leda till kritiska fel såsom meningslösa pekarskapanden och oväntad kopiering av funktioner för andra strukturer.

Så, hur ser exemplarisk kod ut?

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}

Det är dock möjligt att få det att se ut som arv i Go också. Ett exempel på detta är anonym inbäddning. Detta är möjligt genom att deklarera den interna strukturen som en namnlös struktur. I sådana fall är det möjligt att komma åt fälten i understrukturen utan att explicit specificera dem. Genom att använda detta mönster för att promovera fält i understrukturen till den överordnade strukturen kan läsbarheten förbättras i vissa fall. Det rekommenderas dock att inte använda detta när understrukturen explicit behöver visas.

 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 dessa skillnader:

1w.PrintAll() // Anropas automatiskt via den namnlösa strukturen, inte w.Friut.PrintAll()

De viktiga punkterna i båda exemplen är följande:

  • Håll main enkel, funktioner baserade på funktionalitet.
  • Använd olika objekt för olika strukturer.
  • Använd interna strukturer när delning krävs.

Vilka fördelar har en sådan programmeringsfilosofi?

Fördelar

  • Tydlig åtskillnad mellan metoder som behöver delas och de som inte behöver det.
  • Fördelning av ansvar till individuella strukturer och metoder.
  • Strukturellt åtskild kod baserad på nödvändiga funktionsspecifikationer.

Till 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 vid det möjliggör det explicit programmering.

Sammanfattning

  • Isolera ansvar.
  • Dela upp i detaljerade strukturer.
  • Metoder ska inte förstås som Javas abstrakta klasser.
  • Programmera explicit och konkret. Go-språket är enklare och tydligare än den traditionella OOP-modellen och bör hanteras individuellt. Istället för att programmera extensivt, bör vi skriva koden stegvis och strukturellt åtskilt.