GoSuda

Go-rajapinta ei ole perintö

By Yunjin Lee
views ...

Yleiskatsaus

Go-rajapintojen avulla on helppoa, että useilla rakenteilla on funktioita, joilla on samat argumentit ja palautusarvot, mutta tämä eroaa tavasta, jolla Java-kielen extends-avainsana asianmukaisesti laajentaa ja ylikirjoittaa sisäisten funktioiden toimintaa. Go:n kompositorisen koodin uudelleenkäytön asianmukainen ymmärtäminen on välttämätöntä, jotta sitä ei sekoiteta perintään, mutta täydellisen teoreettisen ymmärryksen saavuttaminen alusta alkaen on vaikeaa. Tarkastellaan asiaa virhealtteiden skenaarioiden avulla.

Yleiset virheet

Aloittelijat saattavat tehdä seuraavia 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ällainen koodi saattaa vaikuttaa ongelmattomalta, jos virheellisesti oletetaan, että Go noudattaa perinteistä perintää. Kuitenkin sen tuloste on 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ässä Go:n toiminta selvenee.

1watermelon := apple

Tämä koodi ei muunna Applea Watermelon-luokaksi. Sen sijaan watermelon on pelkästään osoitin apple-muuttujaan.

Tässä korostetaan jälleen kerran, että Go ei noudata perinteistä perinnän käsitettä.

Jos koodia kirjoitetaan tämän väärinymmärryksen vallitessa, seurauksena voi olla kriittisiä virheitä, kuten merkityksettömien osoittimien luominen ja odottamaton funktioiden kopiointi muille rakenteille.

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}

Kuitenkin Go-kielessä on mahdollista saada aikaan perinnän kaltainen toiminnallisuus. Tämä on mahdollista anonyymin upotuksen avulla. Se onnistuu julistamalla sisäinen rakenne nimettömäksi rakenteeksi. Tällöin alahuomiorakenteen kenttiä voidaan käyttää ilman nimenomaista mainintaa. Hyödyntämällä tätä mallia, jossa alahuomiorakenteen kentät ylennetään ylähuomiorakenteeseen, luettavuutta voidaan parantaa tapauskohtaisesti. Kuitenkin, jos alahuomiorakenne on esitettävä eksplisiittisesti, sen käyttöä ei suositella.

 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 seuraavat erot:

 1w.PrintAll() // Automaattinen ylennetty kutsu nimettömän rakenteen kautta, ei w.Fruit.PrintAll()
 2Molemmissa esimerkeissä tärkeät kohdat ovat seuraavat:
 3- main-funktio pidetään yksinkertaisena, funktiot jaetaan toiminnallisuuden mukaan.
 4- Jos kyseessä on eri rakenne, käytetään eri objektia.
 5- Jos jakaminen on tarpeen, käytetään sisäistä rakennetta.
 6
 7Mitä etuja tällaisella ohjelmointifilosofialla on?
 8
 9## Edut
10
11- Selkeä erottelu jaettavien ja ei-jaettavien metodien välillä.
12- Vastuiden jakaminen yksittäisten rakenteiden ja metodien välillä.
13- Rakenteellisesti eroteltu koodi tarvittavien toiminnallisten spesifikaatioiden mukaisesti.
14
15Aluksi Go-kieli saattaa tuntua vieraalta, koska se eroaa perinteisestä olio-ohjelmoinnista, mutta siihen tottumalla on mahdollista ohjelmoida eksplisiittisesti.
16
17## Yhteenveto
18- Eristetään vastuualueet.
19- Jaetaan yksityiskohtaisesti rakenteittain.
20- Metodia ei pidä ymmärtää kuten Javan abstraktia luokkaa.
21- Ohjelmoidaan eksplisiittisesti ja konkreettisesti.
22Go-kieltä tulisi käsitellä yksinkertaisemmin ja yksilöllisemmin kuin perinteistä olio-ohjelmoinnin mallia. Sen sijaan, että pyrittäisiin laajennettavaan ohjelmointiin, koodi tulisi kirjoittaa vaiheittain ja rakenteellisesti erotellen.
23