Go Interfaces sind keine Vererbung
개요
Go Interfaces ermöglichen es auf einfache Weise, dass mehrere Strukturen Funktionen mit denselben Argumenten und Rückgabewerten haben, was sich jedoch von der Methode unterscheidet, bei der das Verhalten interner Funktionen wie beim extends
-Schlüsselwort in Java entsprechend erweitert und überschrieben wird. Obwohl ein korrektes Verständnis der kompositionellen Code-Wiederverwendung in Go dazu beitragen sollte, Verwechslungen mit Vererbung zu vermeiden, ist ein perfektes theoretisches Verständnis von Anfang an schwierig. Lassen Sie uns dies anhand von häufigen Fehlerszenarien erläutern.
자주 하는 실수
Anfänger können die folgenden Fehler machen:
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}
Dieser Code scheint unproblematisch zu sein, wenn man fälschlicherweise annimmt, dass Go traditionelle Vererbung unterstützt. Die Ausgabe ist jedoch wie folgt:
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
Hier wird das Verhalten von Go deutlich:
1watermelon := apple
Dieser Code konvertiert Apple
überhaupt nicht direkt in eine Watermelon
-Klasse.watermelon
ist lediglich ein Pointer auf apple
.
Hier wird nochmals betont: Go folgt nicht dem traditionellen Konzept der Vererbung.
Wenn Code mit diesem Missverständnis geschrieben wird, führt dies zu kritischen Fehlern wie der unnötigen Erzeugung von Pointern oder dem unerwarteten Kopieren von Funktionen für andere Strukturen.
Was wäre dann ein vorbildlicher Code?
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}
Dennoch ist es in Go möglich, Vererbung ähnliches Verhalten zu erzielen. Ein Beispiel hierfür ist das anonyme Embedding. Dies wird möglich, wenn eine interne Struktur als unbenannte Struktur deklariert wird. In solchen Fällen ist es möglich, Felder der untergeordneten Struktur ohne explizite Angabe zu verwenden und direkt darauf zuzugreifen. Dieses Muster, bei dem Felder einer untergeordneten Struktur in die übergeordnete Struktur hochgestuft werden, kann in bestimmten Fällen die Lesbarkeit verbessern. Es wird jedoch nicht empfohlen, es zu verwenden, wenn die untergeordnete Struktur explizit sichtbar sein sollte.
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 diesem Beispiel gibt es folgende Unterschiede:
1w.PrintAll() // w.Friut.PrintAll() ist nicht der Fall, sondern ein automatischer Aufruf durch eine unbenannte Struktur
2Die wichtigen Punkte in beiden Beispielen sind folgende:
3- `main` ist vereinfacht, Funktionen sind nach Funktionalität getrennt.
4- Wenn es sich um unterschiedliche Strukturen handelt, dann um unterschiedliche Objekte.
5- Wenn Sharing erforderlich ist, verwenden Sie interne Strukturen.
6
7Welche Vorteile bietet eine solche Programmierphilosophie?
8
9## 이점
10
11- Klare Unterscheidung zwischen Methoden, die geteilt werden müssen, und solchen, die es nicht müssen.
12- Trennung der Verantwortlichkeiten in einzelnen Strukturen und Methoden.
13- Strukturell getrennter Code gemäß den erforderlichen Funktionsspezifikationen.
14
15
16Anfangs mag Go ungewohnt erscheinen, da es sich von traditioneller OOP unterscheidet, aber mit der Gewöhnung ermöglicht es explizite Programmierung.
17
18## 요약
19- Isolieren Sie die Verantwortlichkeiten.
20- Teilen Sie detailliert nach Struktureinheiten auf.
21- Methoden sollten nicht wie abstrakte Klassen in Java verstanden werden.
22- Führen Sie eine explizite und konkrete Programmierung durch.
23Die Programmiersprache Go sollte einfacher und individueller behandelt werden als traditionelle OOP-Modelle. Anstatt erweiterbar zu programmieren, sollten wir schrittweise und strukturell getrennten Code schreiben.
24