GoSuda

Go rozhrania nie sú dedičnosťou

By Yunjin Lee
views ...

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.