Interfețele Go nu sunt pentru moștenire
Privire de ansamblu
Interfețele Go permit cu ușurință ca multiple structuri să aibă funcții cu aceleași argumente și valori de retur, însă diferă de modul în care cuvântul cheie extends
din Java extinde și suprascrie comportamentul funcțiilor interne. Înțelegerea corectă a reutilizării compoziționale a codului în Go ar trebui să prevină confuzia cu moștenirea, dar o înțelegere teoretică perfectă de la început este dificilă. Să analizăm scenarii în care se fac erori frecvente.
Erori frecvente
Începătorii pot face următoarele greșeli:
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}
Acest cod pare să nu aibă probleme dacă se presupune în mod eronat că Go urmează moștenirea tradițională. Totuși, rezultatul său este următorul:
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
Aici, comportamentul Go devine pur și simplu clar.
1watermelon := apple
Acest cod nu convertește deloc un Apple într-o clasă Watermelon. Pur și simplu, watermelon este doar un pointer către apple.
Subliniem din nou, Go nu urmează conceptul tradițional de moștenire.
Dacă scrieți cod cu această înțelegere greșită, veți crea pointeri inutili și erori critice, cum ar fi copierea neașteptată de funcții pentru alte structuri.
Atunci, cum ar arăta un cod exemplar?
Exemplu adecvat în limbajul 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}
Cu toate acestea, în Go este posibil să facem să arate ca o moștenire. Acesta este un exemplu de încorporare anonimă. Acest lucru este posibil prin declararea unei structuri interne ca o structură fără nume. În aceste cazuri, este posibil să accesați câmpurile structurii interne fără a le specifica în mod explicit. Folosind acest model de promovare a câmpurilor structurii interne către structura superioară, lizibilitatea poate fi îmbunătățită în anumite cazuri. Cu toate acestea, nu se recomandă utilizarea acestuia în cazurile în care structura internă trebuie afișată 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}
În acest exemplu, există următoarele diferențe:
1w.PrintAll() // Apelul de promovare automată prin structura fără nume, nu w.Friut.PrintAll()
2Ambele exemple au următoarele puncte importante:
3- main este simplificat, funcțiile sunt pe funcționalități
4- Obiecte diferite pentru structuri diferite
5- Utilizați structuri interne atunci când este necesară partajarea
6
7Ce avantaje oferă această filozofie de programare?
8
9## Avantaje
10
11- Distincție clară între metodele care necesită partajare și cele care nu necesită
12- Separarea responsabilităților pentru structuri și metode individuale
13- Cod structurat separat, conform specificațiilor funcționalităților necesare
14
15Inițial, limbajul Go poate părea neobișnuit, deoarece este diferit de OOP-ul tradițional, dar odată ce vă obișnuiți, programarea explicită devine posibilă.
16
17## Rezumat
18- Izolați responsabilitățile
19- Împărțiți în detaliu pe unități de structură
20- Metodele nu ar trebui înțelese ca clase abstracte din Java
21- Programați explicit și concret
22Limbajul Go ar trebui tratat mai simplu și individual decât modelul OOP tradițional. În loc să programăm extensiv, să scriem codul în etape și să îl separăm structural.
23