GoSuda

Le interfacce Go non sono ereditarietà

By Yunjin Lee
views ...

Panoramica

Le interfacce Go consentono a diverse struct di avere facilmente funzioni con gli stessi argomenti e valori di ritorno, ma ciò differisce dal modo in cui la parola chiave extends di Java estende e sovrascrive adeguatamente il comportamento delle funzioni interne. Non si confonderà con l'ereditarietà solo se si comprende correttamente il riutilizzo del codice compositivo di Go, ma è difficile ottenere una comprensione teoricamente perfetta fin dall'inizio. Esaminiamo gli scenari in cui è facile commettere errori.

Errori comuni

I principianti possono commettere i seguenti errori.

 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}

Questo codice potrebbe sembrare privo di problemi se si suppone erroneamente che Go segua l'ereditarietà tradizionale. Tuttavia, il suo output è il seguente:

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

Qui, il comportamento di Go diventa semplicemente chiaro.

1watermelon := apple

Questo codice non converte affatto una Apple in una classe Watermelon. Piuttosto, watermelon è solo un puntatore ad apple.

Vorrei sottolineare ancora una volta che Go non segue il concetto tradizionale di ereditarietà.

Se si scrive codice con questa incomprensione, si verificheranno errori critici come la creazione di puntatori inutili e la copia inaspettata di funzioni per altre struct.

Qual è, allora, un codice esemplare?

Esempio appropriato in 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}

Tuttavia, anche in Go è possibile far sì che sembri esserci ereditarietà. Un esempio è l'embedding anonimo. Ciò è possibile dichiarando la struct interna come una struct senza nome. In questi casi, è possibile accedere ai campi della struct inferiore anche senza specificarli esplicitamente. Utilizzando questo pattern per promuovere i campi della struct inferiore alla struct superiore, è possibile migliorare la leggibilità in alcuni casi. Tuttavia, si sconsiglia di usarlo quando la struct inferiore deve essere mostrata esplicitamente.

 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}

In questo esempio, ci sono le seguenti differenze:

1w.PrintAll() // Chiamata di promozione automatica tramite una struct senza nome, non w.Friut.PrintAll()

Il punto importante in entrambi gli esempi è il seguente:

  • main deve essere conciso, le funzioni devono essere separate per funzionalità.
  • Se si tratta di struct diverse, usare oggetti diversi.
  • Se è necessaria la condivisione, usare struct interne.

Quali sono i vantaggi di una tale filosofia di programmazione?

Vantaggi

  • Chiare distinzioni tra metodi che richiedono condivisione e quelli che non la richiedono.
  • Separazione delle responsabilità per singole struct e metodi.
  • Codice strutturalmente separato in base alle specifiche delle funzionalità richieste.

Inizialmente, il linguaggio Go potrebbe essere insolito a causa della sua differenza dall'OOP tradizionale, ma una volta acquisita familiarità, consente una programmazione esplicita.

Riepilogo

  • Isoliamo le responsabilità.
  • Suddividiamo in dettaglio per unità di struct.
  • I metodi non devono essere intesi come classi astratte di Java.
  • Pratichiamo una programmazione esplicita e concreta. Il linguaggio Go dovrebbe essere trattato in modo più semplice e individuale rispetto ai modelli OOP tradizionali. Invece di programmare in modo estensivo, dovremmo scrivere il codice in modo graduale e strutturalmente separato.