Einbettung vs. Aggregation
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 38 von 107.
Du hast gelernt, dass Einbettung Felder und Methoden in die äußere Struktur hebt. Go unterstützt jedoch auch Aggregation, bei der du eine andere Struktur als benanntes Feld einfügst. Zu verstehen, wann welcher Ansatz verwendet werden sollte, ist entscheidend für gutes Design.
Beim Embedding lässt du den Feldnamen weg, und die Mitglieder des inneren Typs werden direkt zugänglich:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // Einbettung – kein Feldname
}
d := Document{}
d.Write() // direkter Zugriff
Bei der Aggregation gibst du dem Feld einen expliziten Namen, sodass dieser Name erforderlich ist, um auf den inneren Typ zuzugreifen:
type Document struct {
writer Writer // Aggregation - benanntes Feld
}
d := Document{writer: Writer{}}
d.writer.Write() // muss den Feldnamen verwenden
Der entscheidende Unterschied ist die Beziehung, die du ausdrückst. Einbetten deutet auf eine „ist-ein“-Beziehung hin, bei der der äußere Typ die Identität des inneren Typs annimmt. Aggregation deutet auf eine „hat-ein“-Beziehung hin, bei der der äußere Typ den inneren Typ lediglich enthält.
Verwende Embedding, wenn der äußere Typ die von einem inneren Typ implementierten Schnittstellen erfüllen soll oder wenn der direkte Methodenzugriff die API übersichtlicher macht. Verwende Aggregation, wenn der innere Typ ein Implementierungsdetail ist, das nicht offengelegt werden sollte, oder wenn du ausdrücklich kontrollieren möchtest, wie auf den inneren Typ zugegriffen wird.
Aufgabe
EinfachErstellen wir ein System zur Berichtserstellung, das veranschaulicht, wann Einbettung statt Aggregation verwendet werden sollte. Du erstellst ein System, bei dem einige Komponenten direkt zugänglich sein sollten (Einbettung), während andere interne Implementierungsdetails bleiben sollten (Aggregation).
Du organisierst deinen Code über drei Dateien:
formatter.go: Erstelle eineFormatter-Struktur mit einemStyle-Feld (String) und einerFormat(text string) string-Methode, die den Text mit dem Stil umschlossen zurückgibt, wie[Style] text [/Style]. Dies ist eine interne Hilfskomponente, die nicht direkt offengelegt werden sollte.report.go: Erstelle zwei Strukturen, die die Berichtserstellung unterschiedlich handhaben:- Eine
Metadata-Struktur mit den FeldernAuthorundDate. Sie stellt öffentliche Informationen dar, die direkt zugänglich sein sollten - Eine
Report-Struktur mit einemTitle-Feld, dieMetadataeinbettet (für den direkten Zugriff auf Autor und Datum), aber fürFormatterAggregation verwendet (als internes Implementierungsdetail mit einem benannten Feldformatter)
Generate() string-Struktur eineReport-Methode hinzu, die den internen Formatter verwendet, um den Titel zu formatieren, und die Metadaten einschließt.- Eine
main.go: Lies die Berichtsdaten aus der Eingabe, erstelle einenReportmit sowohl eingebetteten Metadaten als auch einem aggregierten Formatter. Veranschauliche den Unterschied, indem du direkt über den Report auf den Autor zugreifst (durch Einbettung) und anschließend den formatierten Bericht erzeugst (der intern den verborgenen Formatter verwendet).
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Berichtstitel
- Zeile 2: Name des Autors
- Zeile 3: Datum
- Zeile 4: Formatter-Stil
Deine Format-Methode sollte Folgendes zurückgeben:
[Style] text [/Style]Deine Generate-Methode sollte Folgendes zurückgeben:
[formatted title]
Author: [Author], Date: [Date]Gib in deiner Hauptdatei drei Zeilen aus:
- Den direkt über den Report abgerufenen Autor (um zu zeigen, dass die Einbettung das Feld verfügbar macht)
- Das über den Namen des eingebetteten Typs abgerufene Datum (
report.Metadata.Date) - Das Ergebnis des Aufrufs von
Generate()
Beispielsweise sollte deine Ausgabe bei Quarterly Results, Jane Doe, 2024-01-15 und BOLD folgendermaßen aussehen:
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15Beachte, wie Author und Date durch die Einbettung direkt über den Report zugänglich sind, während der Formatter verborgen bleibt. Du kannst report.Format() nicht direkt aufrufen, da er mit einem benannten Feld aggregiert wurde. Dies veranschaulicht den Unterschied zwischen der „ist-ein“- und der „hat-ein“-Beziehung in der Praxis.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
title := scanner.Text()
scanner.Scan()
author := scanner.Text()
scanner.Scan()
date := scanner.Text()
scanner.Scan()
style := scanner.Text()
// TODO: Erstelle eine Metadata-Struct mit dem Autor und dem Datum
// TODO: Erstelle einen Formatter mit dem gegebenen Style
// TODO: Erstelle einen Report, der Metadata einbettet und Formatter aggregiert
// TODO: Gib den Autor aus, der direkt über den Report zugänglich ist (Einbettung promoted das Feld)
// TODO: Gib das Datum aus, das über den eingebetteten Typnamen zugänglich ist (report.Metadata.Date)
// TODO: Gib das Ergebnis des Aufrufs von Generate() aus
// Unterdrücke Warnungen zu ungenutzten Variablen (entferne diese, wenn du die Variablen verwendest)
_ = title
_ = author
_ = date
_ = style
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler