Le interfacce Go non sono ereditarietà
Panoramica
L'interfaccia Go consente a più strutture di avere facilmente funzioni con gli stessi argomenti e valori di ritorno, ma differisce dall'estensione e dall'override adeguati del comportamento delle funzioni interne, come avviene con la parola chiave extends
di Java. Se si comprende correttamente il riutilizzo compositivo del codice di Go, non lo si confonderà con l'ereditarietà, ma è difficile raggiungere una comprensione teoricamente perfetta fin dall'inizio. Esploriamo questo concetto con uno scenario comune di errore.
Errori comuni
I principianti potrebbero commettere i seguenti errori.
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}
Questo codice potrebbe sembrare privo di problemi se si suppone erroneamente che Go segua l'ereditarietà tradizionale. Tuttavia, il suo output è il seguente:
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
Qui il comportamento di Go diventa semplicemente chiaro.
1watermelon := apple
Questo codice non converte affatto Apple
nella classe Watermelon
.
Piuttosto, watermelon
è solo un puntatore a apple
.
Qui, lo sottolineiamo di nuovo, Go non segue il concetto di ereditarietà tradizionale.
Se si scrive codice con questa incomprensione, si verificheranno errori critici come la creazione di puntatori inutili o la copia inaspettata di funzioni per altre strutture.
Qual è, allora, il codice esemplare?
Un esempio appropriato in Go
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}
Tuttavia, in Go è possibile far sì che sembri come l'ereditarietà. Un esempio è l'embedding anonimo. Questo è possibile dichiarando la struttura interna come una struttura senza nome. In questi casi, è possibile accedere ai campi della sottostruttura senza nominarli esplicitamente. Utilizzando questo modello per promuovere i campi della sottostruttura alla sovrastruttura, la leggibilità può essere migliorata in alcuni casi. Tuttavia, si sconsiglia di usarlo quando la sottostruttura dovrebbe essere mostrata esplicitamente.
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}
In questo esempio, le differenze sono le seguenti:
1w.PrintAll() // Chiamata automatica di promozione tramite una struttura senza nome, non w.Friut.PrintAll()
2Il punto cruciale in entrambi gli esempi è questo:
3- Il main deve essere semplice, le funzioni devono essere specifiche per la loro funzionalità
4- Se sono strutture diverse, devono essere oggetti diversi
5- Se è necessaria la condivisione, utilizzare una struttura interna
6
7Quali sono i vantaggi di questa filosofia di programmazione?
8
9## Vantaggi
10
11- Chiare distinzioni tra metodi che richiedono condivisione e quelli che non la richiedono
12- Separazione delle responsabilità per singole strutture e metodi
13- Codice strutturalmente separato in base alle specifiche delle funzionalità richieste
14
15Inizialmente, Go potrebbe non essere familiare a causa della sua differenza rispetto all'OOP tradizionale, ma una volta acquisita familiarità, consente una programmazione esplicita.
16
17## Riepilogo
18- Isolare le responsabilità.
19- Suddividere finemente in unità di struttura.
20- I metodi non devono essere intesi come classi astratte di Java.
21- Praticare una programmazione esplicita e concreta.
22Il linguaggio Go dovrebbe essere trattato in modo più semplice e individuale rispetto al modello OOP tradizionale. Invece di programmare in modo estensivo, dovremmo scrivere il codice in modo incrementale e strutturalmente separato.
23