GoSuda

Interfața Go nu este moștenire

By Yunjin Lee
views ...

Prezentare generală

Interfața Go permite ca funcții cu argumente și valori returnate identice să fie ușor disponibile în multiple structuri, însă diferă de abordarea de extindere și suprascriere a comportamentului funcțiilor interne, similar cu cuvântul cheie extends din Java. Deși o înțelegere corectă a reutilizării codului compozițional din Go ar trebui să prevină confuzia cu moștenirea, este dificil să se obțină o înțelegere teoretică perfectă de la început. Să analizăm un scenariu predispus la erori.

Greșeli frecvente

Începătorii pot comite următoarele greșeli:

 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}

Acest cod pare lipsit de probleme dacă se presupune în mod eronat că Go urmează moștenirea tradițională. Cu toate acestea, rezultatul său este următorul:

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

Aici, comportamentul Go devine clar.

1watermelon := apple

Acest cod nu convertește deloc un Apple într-o clasă Watermelon.watermelon este pur și simplu un pointer către apple.

Reiterăm, Go nu urmează conceptul tradițional de moștenire.

Dacă se scrie cod cu această înțelegere greșită, se pot produce erori critice, cum ar fi crearea de pointeri inutili și copierea neașteptată de funcții pentru alte structuri.

Atunci, cum ar arăta un cod exemplar?

Exemplu adecvat în limbajul 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}

Totuși, în Go este posibil să se creeze o aparență de moștenire. Acesta este un exemplu de încorporare anonimă. Este posibil prin declararea structurilor interne ca structuri fără nume. În astfel de cazuri, câmpurile structurii subiacente pot fi accesate direct chiar și fără a fi specificate explicit. Folosirea acestui model de promovare a câmpurilor structurii subiacente la structura superioară poate îmbunătăți lizibilitatea în anumite situații. Cu toate acestea, se recomandă să nu se utilizeze atunci când structura subiacentă trebuie afișată explicit.

 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}

În acest exemplu, există următoarea diferență:

1w.PrintAll() // w.Friut.PrintAll() este apelat prin promovare automată printr-o structură fără nume, nu w.Friut.PrintAll()

Ambele exemple subliniază puncte importante:

  • main este simplificat, iar funcțiile sunt separate pe funcționalități.
  • Dacă sunt structuri diferite, se utilizează obiecte diferite.
  • Dacă este necesară partajarea, se utilizează structuri interne.

Ce avantaje prezintă o astfel de filosofie de programare?

Avantaje

  • Diferențiere clară între metodele care necesită partajare și cele care nu o necesită.
  • Separarea responsabilităților pentru structuri și metode individuale.
  • Cod structurat separat în funcție de specificațiile funcționalității necesare.

La început, limbajul Go poate părea neobișnuit, deoarece diferă de OOP-ul tradițional, dar odată ce te obișnuiești, permite o programare explicită.

Rezumat

  • Să izolăm responsabilitățile.
  • Să divizăm detaliat la nivel de structură.
  • Metodele nu ar trebui înțelese ca clase abstracte din Java.
  • Să programăm explicit și concret. Limbajul Go ar trebui tratat mai simplu și individual decât modelul OOP tradițional. Să scriem codul separat structural și treptat, în loc să programăm extensiv.