Go rozhrania nie sú dedičnosťou
Prehľad
Go rozhrania umožňujú jednoducho implementovať funkcie s rovnakými argumentmi a návratovými hodnotami v rôznych štruktúrach, avšak líšia sa od spôsobu, akým kľúčové slovo extends
v Jave rozširuje a prekrýva správanie vnútorných funkcií. Ak chcete správne pochopiť kompozičné opätovné použitie kódu v Go a nezamieňať si ho s dedičnosťou, je nevyhnutné mať komplexné teoretické porozumenie, ktoré je však od začiatku náročné dosiahnuť. Pozrime sa na bežné chyby a scenáre.
Časté chyby
Začiatočníci sa môžu dopúšťať nasledujúcich chýb.
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 // Tu dochádza k chybe. watermelon sa stáva ukazovateľom na 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}
Tento kód sa môže zdať bezproblémový, ak sa mylne domnievame, že Go dodržiava tradičnú dedičnosť. Avšak jeho výstup je nasledujúci:
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
Tu sa správanie Go stáva jasným.
1watermelon := apple
Tento kód vôbec nekonvertuje Apple
na triedu Watermelon
.
Namiesto toho je watermelon
iba ukazovateľom na apple
.
Opätovne zdôrazňujem, Go nedodržiava tradičný koncept dedičnosti.
Ak programujete s týmto nedorozumením, môže to viesť k fatálnym chybám, ako je zbytočné vytváranie ukazovateľov alebo neočakávané kopírovanie funkcií pre iné štruktúry.
Aký by teda bol príklad osvedčeného kódu?
Vhodný príklad v jazyku 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 // Vnorenie štruktúry BaseFruit
23}
24
25type Watermelon struct {
26 Label string
27 Fruit BaseFruit // Vnorenie štruktúry BaseFruit
28}
29
30func (b *BaseFruit) PrintAll() {
31 fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
32}
33
34
35const (
36 NO_LABEL = "EMPTY LABEL"
37)
38
39func (a *Apple) SetLabel(lbl string) {
40 a.Fruit.Brix = 14.5;
41 a.Fruit.Name = "apple";
42 lbl_lower := strings.ToLower(lbl)
43 if strings.Contains(lbl_lower, a.Fruit.Name) {
44 fmt.Println("Succeed: Label was ", lbl)
45 a.Label = lbl;
46 } else {
47 fmt.Println("Failed: Label was ", lbl)
48 a.Label = NO_LABEL;
49 }
50 fmt.Printf("Fruit %s label set to %s\n", a.Fruit.Name, a.Label);
51 a.Fruit.PrintAll() // Volanie metódy vnorenej štruktúry
52}
53
54func (w *Watermelon) SetLabel(lbl string) {
55 w.Fruit.Brix = 10;
56 w.Fruit.Name = "Watermelon";
57 lbl_lower := strings.ToLower(lbl)
58 if strings.Contains(lbl_lower, w.Fruit.Name) {
59 w.Label = lbl;
60 } else {
61 w.Label = NO_LABEL;
62 }
63 fmt.Printf("Fruit %s label set to %s\n", w.Fruit.Name, w.Label);
64 w.Fruit.PrintAll() // Volanie metódy vnorenej štruktúry
65}
66
67func main() {
68 apple := new(Apple)
69 watermelon := new(Watermelon)
70 apple.SetLabel("Apple_1")
71 watermelon.SetLabel("WaterMelon_2")
72}
Avšak v Go je možné dosiahnuť vzhľad dedičnosti. Príkladom je anonymné vkladanie. To je možné deklarovaním vnútornej štruktúry bez mena. V takýchto prípadoch je možné pristupovať k poliam podradenej štruktúry bez explicitného uvedenia. Použitím tohto vzoru, ktorý propaguje polia podradenej štruktúry do nadradenej štruktúry, je možné v niektorých prípadoch zlepšiť čitateľnosť. Neodporúča sa však používať, ak je potrebné explicitne zobraziť podradenú štruktúru.
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 // Anonymné vkladanie štruktúry BaseFruit
23}
24
25type Watermelon struct {
26 Label string
27 BaseFruit // Anonymné vkladanie štruktúry BaseFruit
28}
29
30func (b *BaseFruit) PrintAll() {
31 fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
32}
33
34
35const (
36 NO_LABEL = "EMPTY LABEL"
37)
38
39func (a *Apple) SetLabel(lbl string) {
40 a.Brix = 14.5; // Prístup k poľu Brix priamo z Apple vďaka anonymnému vkladaniu
41 a.Name = "apple"; // Prístup k poľu Name priamo z Apple vďaka anonymnému vkladaniu
42 lbl_lower := strings.ToLower(lbl)
43 if strings.Contains(lbl_lower, a.Name) {
44 fmt.Println("Succeed: Label was ", lbl)
45 a.Label = lbl;
46 } else {
47 fmt.Println("Failed: Label was ", lbl)
48 a.Label = NO_LABEL;
49 }
50 fmt.Printf("Fruit %s label set to %s\n", a.Name, a.Label);
51 a.PrintAll() // Volanie metódy PrintAll priamo z Apple vďaka anonymnému vkladaniu
52}
53
54func (w *Watermelon) SetLabel(lbl string) {
55 w.Brix = 10; // Prístup k poľu Brix priamo z Watermelon vďaka anonymnému vkladaniu
56 w.Name = "Watermelon"; // Prístup k poľu Name priamo z Watermelon vďaka anonymnému vkladaniu
57 lbl_lower := strings.ToLower(lbl)
58 if strings.Contains(lbl_lower, w.Name) {
59 w.Label = lbl;
60 } else {
61 w.Label = NO_LABEL;
62 }
63 fmt.Printf("Fruit %s label set to %s\n", w.Name, w.Label);
64 w.PrintAll() // Volanie metódy PrintAll priamo z Watermelon vďaka anonymnému vkladaniu
65}
66
67func main() {
68 apple := new(Apple)
69 watermelon := new(Watermelon)
70 apple.SetLabel("Apple_1")
71 watermelon.SetLabel("WaterMelon_2")
72}
V tomto príklade sú tieto rozdiely:
1w.PrintAll() // Automatické volanie metódy prostredníctvom anonymnej štruktúry, nie w.Fruit.PrintAll()
Dôležité body v oboch príkladoch sú nasledovné:
main
by mala byť zjednodušená, funkcie by mali byť rozdelené podľa funkcionality.- Ak ide o rôzne štruktúry, mali by to byť rôzne objekty.
- Ak je potrebné zdieľanie, použite vnútorné štruktúry.
Aké výhody prináša táto filozofia programovania?
Výhody
- Jasné rozlíšenie medzi metódami, ktoré je potrebné zdieľať, a tými, ktoré nie.
- Oddelenie zodpovednosti pre jednotlivé štruktúry a metódy.
- Štruktúrne oddelený kód podľa požadovaných funkčných špecifikácií.
Spočiatku sa jazyk Go môže zdať nezvyčajný, pretože sa líši od tradičného OOP, ale keď si naň zvyknete, umožňuje explicitné programovanie.
Zhrnutie
- Izolujte zodpovednosť.
- Rozdeľte kód do detailných jednotiek štruktúr.
- Metódy by sa nemali chápať ako abstraktné triedy v Jave.
- Programujte explicitne a konkrétne. Jazyk Go by sa mal chápať ako jednoduchší a individuálnejší než tradičný OOP model. Namiesto rozsiahleho programovania by sme mali kód písať postupne a štruktúrne oddelene.