Menu
Coddy logo textTech

Warum Go keine Vererbung hat

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 34 von 107.

Traditionelle objektorientierte Sprachen wie Java und C++ verwenden Vererbung, um Code zwischen Typen gemeinsam zu nutzen. Eine Kindklasse erweitert eine Elternklasse und erbt alle ihre Felder und Methoden. Go lässt diese Funktionalität bewusst weg.

Vererbung erzeugt eine enge Kopplung zwischen Typen. Wenn sich eine Elternklasse ändert, sind alle Unterklassen betroffen.

Tiefe Vererbungshierarchien werden schwer verständlich und wartbar. Das „Problem der fragilen Basisklasse“ tritt auf, wenn Änderungen an einer Basisklasse unerwartet abgeleitete Klassen beschädigen.

Die Designer von Go haben einen anderen Weg gewählt: Komposition statt Vererbung. Anstatt zu sagen: „Ein Dog ist ein Animal“, ermutigt Go dich zu sagen: „Ein Dog has Animal-ähnliche Verhaltensweisen.“ Diese subtile Verschiebung führt zu flexiblerem und wartungsfreundlicherem Code.

Go erreicht die Wiederverwendung von Code durch zwei Mechanismen, die du bereits kennengelernt hast:

  • Interfaces definieren Verhaltensverträge ohne Implementierungsdetails
  • Struct-Einbettung ermöglicht es Typen, andere Typen einzuschließen und deren Methoden wiederzuverwenden

Betrachte diesen Vergleich. In der traditionellen OOP würdest du möglicherweise class Dog extends Animal schreiben. In Go bettest du eine Animal-Struktur in Dog ein und implementierst gemeinsame Schnittstellen. Das Ergebnis ist eine ähnliche Funktionalität mit einer geringeren Kopplung zwischen den Typen.

Dieses Kapitel untersucht die Struktur-Einbettung ausführlich und zeigt, wie Go die Vorteile der Vererbung ohne deren Nachteile erzielt.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Benachrichtigungssystem, das den Kompositionsansatz von Go anstelle von Vererbung demonstriert. Du erstellst Typen, die ihr Verhalten über Interfaces und Struct-Einbettung statt über Klassenhierarchien teilen.

Du organisierst deinen Code über drei Dateien:

  • notifier.go: Definiere ein Notifier-Interface mit einer einzigen Methode Notify(message string) string. Erstelle außerdem eine BaseNotifier-Struct mit einem Name-Feld, die von anderen Typen eingebettet wird. Gib BaseNotifier eine Methode namens Format(message string) string, die die Nachricht mit dem Namen des Benachrichtigers in eckigen Klammern als Präfix zurückgibt.
  • channels.go: Erstelle zwei Typen für Benachrichtigungskanäle, die BaseNotifier einbetten und das Notifier-Interface implementieren:
    • EmailNotifier mit einem zusätzlichen Address-Feld
    • SMSNotifier mit einem zusätzlichen Phone-Feld
    Die Notify-Methode jedes Typs sollte die eingebettete Format-Methode verwenden und die spezifischen Kanalinformationen in die Ausgabe aufnehmen.
  • main.go: Erstelle eine Funktion namens SendAlert, die ein beliebiges Notifier und eine Nachricht akzeptiert und anschließend das Ergebnis des Aufrufs von Notify zurückgibt. Lies die Benachrichtigungsdetails aus der Eingabe, erstelle beide Arten von Benachrichtigern und demonstriere, wie sie über das Interface austauschbar verwendet werden können.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Name des Benachrichtigers für E-Mail
  • Zeile 2: E-Mail-Adresse
  • Zeile 3: Name des Benachrichtigers für SMS
  • Zeile 4: Telefonnummer
  • Zeile 5: Benachrichtigungsnachricht

Deine Format-Methode auf BaseNotifier sollte Folgendes zurückgeben:

[Name] message

Deine Notify-Methoden sollten Folgendes zurückgeben:

  • EmailNotifier: Email to [Address]: [formatted message]
  • SMSNotifier: SMS to [Phone]: [formatted message]

Wenn beispielsweise Alerts, user@mail.com, Urgent, 555-1234 und Server down gegeben sind, sollte deine Ausgabe wie folgt aussehen:

Email to user@mail.com: [Alerts] Server down
SMS to 555-1234: [Urgent] Server down

Beachte, wie beide Benachrichtigertypen die Format-Methode von BaseNotifier durch Einbettung wiederverwenden, während jeder seine eigene Notify-Implementierung bereitstellt. Die Funktion SendAlert funktioniert mit jedem Notifier, ohne den konkreten Typ zu kennen. Das ist Komposition statt Vererbung in der Praxis.

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
)

// SendAlert akzeptiert einen beliebigen Notifier und eine Nachricht und gibt das Ergebnis des Aufrufs von Notify zurück
// TODO: Implementiere die SendAlert-Funktion

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// E-Mail-Notifier-Namen lesen
	scanner.Scan()
	emailName := scanner.Text()
	
	// E-Mail-Adresse lesen
	scanner.Scan()
	emailAddress := scanner.Text()
	
	// SMS-Notifier-Namen lesen
	scanner.Scan()
	smsName := scanner.Text()
	
	// Telefonnummer lesen
	scanner.Scan()
	phoneNumber := scanner.Text()
	
	// Alarmnachricht lesen
	scanner.Scan()
	alertMessage := scanner.Text()
	
	// TODO: Erstelle einen EmailNotifier mit emailName und emailAddress
	
	// TODO: Erstelle einen SMSNotifier mit smsName und phoneNumber
	
	// TODO: Verwende SendAlert, um die alertMessage über beide Notifier zu senden
	// und gib die Ergebnisse aus
	
	// Unterdrücke Warnungen zu ungenutzten Variablen (entferne diese, wenn du die Variablen verwendest)
	_ = emailName
	_ = emailAddress
	_ = smsName
	_ = phoneNumber
	_ = alertMessage
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Go-Compiler