Go-rajapinta ei ole perintö
Yleiskatsaus
Go-rajapintojen avulla useilla rakennerakenteilla voi helposti olla samat argumentit ja palautusarvot, mutta tämä eroaa Javan extends-avaisanan kaltaisesta tavasta, jossa sisäisen funktion toimintaa laajennetaan ja ohitetaan asianmukaisesti. Vaikka Go:n koostumuksellisen koodin uudelleenkäytön oikea ymmärtäminen estäisi sen sekoittamisen perintöön, täydellisen teoreettisen ymmärryksen saavuttaminen alusta alkaen on vaikeaa. Tarkastellaan asiaa yleisten virhetilanteiden kautta.
Yleiset virheet
Aloittelijat saattavat tehdä seuraavanlaisia virheitä:
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}
Tämä koodi saattaa vaikuttaa ongelmattomalta, jos oletetaan, että Go noudattaa perinteistä perintöä. Sen tulostus on kuitenkin seuraava:
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
Tästä Go:n toiminta selviää.
1watermelon := apple
Tämä koodi ei muunna Applea Watermelon-luokaksi.
Sen sijaan watermelon on vain osoitin apple-muuttujaan.
Tässä korostetaan uudelleen, että Go ei noudata perinteistä perinnön käsitettä.
Jos koodia kirjoitetaan tämän väärinkäsityksen vallassa, se johtaa kriittisiin virheisiin, kuten turhiin osoittimen luomisiin ja odottamattomiin funktion kopiointeihin muiden rakennerakenteiden vuoksi.
Millainen olisi esimerkillinen koodi?
Asianmukainen esimerkki Go-kielessä
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}
Go:ssa on kuitenkin mahdollista saada aikaan perinnön kaltainen rakenne. Esimerkkinä on anonyymi upottaminen. Tämä on mahdollista, kun sisäinen rakenne julistetaan nimettömäksi rakenteeksi. Tällaisissa tapauksissa alirakenteen kenttiä voidaan käyttää ilman nimenomaista mainintaa. Käyttämällä tätä mallia, jossa alirakenteen kentät ylennetään ylätason rakenteeseen, luettavuutta voidaan parantaa tapauskohtaisesti. On kuitenkin suositeltavaa olla käyttämättä tätä, jos alirakenne on näytettävä eksplisiittisesti.
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}
Tässä esimerkissä on seuraavanlaisia eroja:
1w.PrintAll() // w.Friut.PrintAll():n sijaan automaattinen ylennyskutsu nimettömän rakenteen kautta
Molempien esimerkkien tärkeimmät kohdat ovat seuraavat:
mainon yksinkertainen, ja funktiot ovat toiminnallisuuden mukaisia.- Jos kyseessä on eri rakenne, käytä eri objektia.
- Jos jakaminen on tarpeen, käytä sisäistä rakennetta.
Mitä hyötyä tällaisesta ohjelmointifilosofiasta on?
Edut
- Selkeä erottelu jaettavien ja jakamattomien metodien välillä.
- Vastuiden erottelu yksittäisten rakenteiden ja metodien välillä.
- Rakennekohtaisesti erotettu koodi tarvittavien toimintojen määrittelyjen mukaisesti.
Aluksi Go-kieli saattaa tuntua oudolta, koska se eroaa perinteisestä oliosuuntautuneesta ohjelmoinnista, mutta siihen tottumalla on mahdollista ohjelmoida eksplisiittisemmin.
Yhteenveto
- Eristetään vastuut.
- Jaetaan yksityiskohtaisesti rakenneyksiköihin.
- Metodia ei pidä ymmärtää Javan abstraktin luokan tavoin.
- Ohjelmoidaan eksplisiittisesti ja konkreettisesti. Go-kieli on yksinkertaisempi ja sitä tulisi käsitellä yksilöllisemmin kuin perinteistä oliosuuntautunutta ohjelmointimallia. Sen sijaan, että ohjelmoisimme laajennettavasti, kirjoittakaamme koodia vaiheittain ja rakenteellisesti eroteltuna.