Les interfaces Go ne sont pas de l'héritage
Aperçu
Les interfaces en Go permettent facilement à plusieurs structures de posséder des fonctions avec les mêmes arguments et valeurs de retour, mais cela diffère de la manière dont le mot-clé extends
de Java étend et surcharge de manière appropriée le comportement de ces fonctions internes. Pour éviter toute confusion avec l'héritage, il est essentiel de bien comprendre la réutilisation du code par composition en Go, mais une compréhension théorique parfaite dès le début est difficile. Examinons des scénarios propices aux erreurs.
Erreurs courantes
Les débutants peuvent commettre les erreurs suivantes.
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}
Ce code peut sembler correct si l'on suppose que Go suit l'héritage traditionnel. Cependant, son résultat est le suivant :
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
Ici, le comportement de Go devient clair.
1watermelon := apple
Ce code ne convertit pas du tout Apple
en une classe Watermelon
.
Il s'agit simplement d'un pointeur de watermelon
vers apple
.
Il est important de souligner à nouveau que Go ne suit pas le concept d'héritage traditionnel.
Si l'on écrit du code en partant de cette incompréhension, des erreurs critiques peuvent survenir, telles que la création de pointeurs inutiles ou la copie inattendue de fonctions pour d'autres structures.
Alors, quel serait le code exemplaire ?
Exemple approprié en 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}
Cependant, il est possible en Go de donner l'apparence de l'héritage. Ceci est un exemple d'intégration anonyme. Cela est possible en déclarant la structure interne comme une structure sans nom. Dans de tels cas, il est possible d'accéder directement aux champs de la sous-structure sans avoir à les spécifier explicitement. L'utilisation de ce modèle, qui consiste à promouvoir les champs de la sous-structure vers la structure parente, peut améliorer la lisibilité dans certains cas. Cependant, il est recommandé de ne pas l'utiliser lorsque la sous-structure doit être affichée explicitement.
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}
Dans cet exemple, il existe les différences suivantes :
1w.PrintAll() // Appel de promotion automatique via la structure anonyme, et non w.Friut.PrintAll()
2Les points importants dans les deux exemples sont les suivants :
3- main doit être concis, les fonctions par fonctionnalité
4- Si les structures sont différentes, utilisez des objets différents
5- Si le partage est nécessaire, utilisez des structures internes
6
7Quels sont les avantages de cette philosophie de programmation ?
8
9## Avantages
10
11- Distinction claire entre les méthodes nécessitant un partage et celles qui n'en nécessitent pas
12- Séparation des responsabilités pour les structures et méthodes individuelles
13- Code structuré et séparé selon les spécifications fonctionnelles requises
14
15Au début, Go peut sembler peu familier car il diffère de la POO traditionnelle, mais une fois habitué, il permet une programmation explicite.
16
17## Résumé
18- Isoler les responsabilités
19- Diviser en structures détaillées
20- Les méthodes ne doivent pas être comprises comme des classes abstraites de Java
21- Pratiquer une programmation explicite et concrète
22Le langage Go doit être traité de manière simple, claire et individuelle, par opposition au modèle POO traditionnel. Plutôt que de programmer de manière extensive, adoptons une approche progressive et structurellement séparée.
23