---
id: ebf46089667a6469b258b8e52e57571f
author: Yunjin Lee
title: 'Go-Schnittstellen und Struktureinbettung: Unterschiede zur Klassenvererbung und kompositionsbasiertes Design'
description: Dieser Text vergleicht die Klassenvererbung in Java/C++ mit dem strukturellen Typisieren von Go-Interfaces und erläutert anhand von Codebeispielen das Kompositionsmuster (Composition) unter Verwendung von Struktur-Embedding und Methodendelegation.
language: de
date: 2025-07-17T10:40:02.284435348Z
path: /blog/posts/go-interfaces-are-not-inheritance-zd04c1084
category: language
---

# Überblick

Go-Interfaces erleichtern es, dass mehrere Structs Funktionen mit denselben Argumenten und Rückgabewerten besitzen, unterscheiden sich jedoch von der Art, wie Java mit dem Schlüsselwort extends das Verhalten dieser Methoden erweitert und überschreibt. Um die kompositionelle Code-Wiederverwendung in Go richtig zu verstehen, sollte man sie nicht mit Vererbung verwechseln; doch ein theoretisch vollständiges Verständnis von Grund auf ist schwer zu erlangen. Lassen Sie uns anhand von Szenarien, in denen man leicht Fehler macht, darüber sprechen.

## Häufige Fehler

Anfänger können Fehler wie die folgenden machen.

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type Apple struct {
	Label string
	Name  string
	Brix  float64
}

type Watermelon struct {
	Label string
	Name  string
	Brix  float64
}

func (a *Apple) PrintAll() {
	fmt.Printf("Fruit: %s, Label: %s, Brix: %v\n", a.Name, a.Label, a.Brix)
}

const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
}

func main() {
	fmt.Println("Inheritance test #1")
	apple := new(Apple)
	watermelon := apple
	apple.SetLabel("Apple_1")
	fmt.Println("Apple, before copied to Watermelon")
	apple.PrintAll()
	watermelon.SetLabel("WaterMelon_2")
	fmt.Println("Apple, after copied to Watermelon")
	apple.PrintAll()
	fmt.Println("Watermelon, which inherited Apple's Method")
	watermelon.PrintAll()
}
```

Wer glaubt, dass Go traditionelle Vererbung kennt, dem scheint dieser Code unproblematisch. Die Ausgabe dieses Codes lautet jedoch wie folgt.

```
Inheritance test #1
Succeed: Label was  Apple_1
Apple, before copied to Watermelon
Fruit: apple, Label: Apple_1, Brix: 14.5
Failed: Label was  WaterMelon_2
Apple, after copied to Watermelon
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
Watermelon, which inherited Apple's Method
Fruit: apple, Label: EMPTY LABEL, Brix: 14.5
```

Hier wird das Verhalten von Go lediglich deutlicher.

```go
watermelon := apple
```

Dieser Code wandelt Apple keineswegs in die Struct Watermelon um.
Vielmehr ist watermelon lediglich ein Zeiger auf apple.

Nochmals betont: **Go folgt nicht dem traditionellen Konzept der Vererbung.**

Wer mit diesem Missverständnis Code schreibt, begeht gravierende Fehler wie die Erzeugung sinnloser Zeiger oder das unbeabsichtigte Kopieren von Funktionen für andere Structs.

Wie sieht also ein musterhafter Code aus?

## Angemessenes Beispiel in Go

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	Fruit BaseFruit
}

type Watermelon struct {
	Label string
	Fruit BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Fruit.Brix 	= 14.5;
	a.Fruit.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Fruit.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Fruit.Name, a.Label);
	a.Fruit.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Fruit.Brix = 10;
	w.Fruit.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Fruit.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Fruit.Name, w.Label);
	w.Fruit.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

Dennoch ist es in Go möglich, Vererbung **ähnlich** erscheinen zu lassen.
Ein Beispiel dafür ist die anonyme Einbettung. Sie ist möglich, wenn man die innere Struct als namenlose Struct deklariert.
In diesem Fall kann man **auch ohne explizite Angabe auf die Felder der untergeordneten Struct** direkt zugreifen.
Mit diesem Muster, bei dem die Felder der untergeordneten Struct in die übergeordnete Struct **hochgestuft** werden, kann die Lesbarkeit je nach Situation verbessert werden. Wenn die untergeordnete Struct jedoch explizit sichtbar gemacht werden muss, wird von der Verwendung abgeraten.

```go
package main
import (
	"fmt"
	"strings"
)

type Fruits interface {
	GetBrix() float64
	GetName() string
	SetLabel()
	GetLabel(string) string
	PrintAll()
}

type BaseFruit struct {
	Name  string
	Brix  float64
}

type Apple struct {
	Label string
	BaseFruit
}

type Watermelon struct {
	Label string
	BaseFruit

}

func (b *BaseFruit) PrintAll() {
	fmt.Printf("Fruit: %s, Brix: %v\n", b.Name, b.Brix)
}


const (
	NO_LABEL = "EMPTY LABEL"
)

func (a *Apple) SetLabel(lbl string) {
	a.Brix 	= 14.5;
	a.Name 	= "apple";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, a.Name) {
		fmt.Println("Succeed: Label was ", lbl)
		a.Label = lbl;
	} else {
		fmt.Println("Failed: Label was ", lbl)
		a.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", a.Name, a.Label);
	a.PrintAll()
}

func (w *Watermelon) SetLabel(lbl string) {
	w.Brix = 10;
	w.Name = "Watermelon";
	lbl_lower := strings.ToLower(lbl)
	if strings.Contains(lbl_lower, w.Name) {
		w.Label = lbl;
	} else {
		w.Label = NO_LABEL;
	}
	fmt.Printf("Fruit %s label set to %s\n", w.Name, w.Label);
	w.PrintAll()
}

func main() {
	apple := new(Apple)
	watermelon := new(Watermelon)
	apple.SetLabel("Apple_1")
	watermelon.SetLabel("WaterMelon_2")
}
```

In diesem Beispiel besteht folgender Unterschied.

```go
w.PrintAll() // Automatischer Aufstieg über die namenlose Struct-Einbettung, statt w.Friut.PrintAll()
```

Beide Beispiele haben folgende wichtige Gesichtspunkte gemeinsam:

- main schlank halten, Funktionen nach Funktionalität aufteilen
- Für unterschiedliche Structs jeweils eigene Objekte verwenden
- Bei Bedarf gemeinsamer Nutzung die innere Struct verwenden

Welche Vorteile hat diese Programmierphilosophie?

## Vorteile

- Klare Unterscheidung zwischen Methoden, die gemeinsam genutzt werden müssen, und solchen, die es nicht müssen
- Trennung der Verantwortlichkeiten auf einzelne Structs und Methoden
- Entsprechend der benötigten Funktionsspezifikation strukturell getrennter Code

Zunächst mag Go ungewohnt sein, da es sich von der traditionellen OOP unterscheidet; mit zunehmender Vertrautheit wird jedoch eine explizite Programmierung möglich.

## Zusammenfassung

- Verantwortlichkeiten isolieren
- Nach Structs im Einzelnen aufteilen
- Methoden dürfen nicht wie abstrakte Klassen in Java verstanden werden
- Explizit und konkret programmieren
  Go ist einfacher und übersichtlicher als das traditionelle OOP-Modell und sollte einzeln behandelt werden. Anstatt erweiterbar zu programmieren, schreiben Sie den Code besser schrittweise und strukturiert getrennt.
