Embedding vs. Aggregation
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 38 von 107.
Sie haben gelernt, dass das Einbetten (Embedding) Felder und Methoden in das äußere Struct befördert. Aber Go unterstützt auch Aggregation, bei der Sie ein anderes Struct als benanntes Feld einfügen. Zu verstehen, wann welcher Ansatz zu verwenden ist, ist der Schlüssel zu gutem Design.
Mit Embedding lassen Sie den Feldnamen weg, und die Member des inneren Typs werden direkt zugänglich:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // Embedding - kein Feldname
}
d := Document{}
d.Write() // direkter Zugriff
Bei der Aggregation geben Sie dem Feld einen expliziten Namen, wobei 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() // Feldname muss verwendet werden
Der entscheidende Unterschied liegt in der Beziehung, die Sie ausdrücken. Embedding suggeriert eine „is-a“-Beziehung, bei der der äußere Typ die Identität des inneren Typs annimmt. Aggregation suggeriert eine „has-a“-Beziehung, bei der der äußere Typ den inneren Typ lediglich enthält.
Verwenden Sie Embedding, wenn der äußere Typ Interfaces erfüllen soll, die vom inneren Typ implementiert werden, oder wenn ein direkter Methodenzugriff die API übersichtlicher macht. Verwenden Sie Aggregation, wenn der innere Typ ein Implementierungsdetail ist, das nicht offengelegt werden sollte, oder wenn Sie eine explizite Kontrolle darüber haben möchten, wie auf den inneren Typ zugegriffen wird.
Aufgabe
EinfachLassen Sie uns ein Berichtserstellungssystem erstellen, das zeigt, wann man Einbettung (Embedding) gegenüber Aggregation verwendet. Sie werden ein System erstellen, bei dem einige Komponenten direkt zugänglich sein sollen (Einbettung), während andere interne Implementierungsdetails bleiben sollen (Aggregation).
Sie werden Ihren Code über drei Dateien organisieren:
formatter.go: Erstellen Sie einFormatter-Struct mit einemStyle-Feld (String) und einerFormat(text string) string-Methode, die den Text umschlossen vom Stil zurückgibt, wie[Style] text [/Style]. Dies wird ein interner Helfer sein, der nicht direkt nach außen hin offengelegt werden sollte.report.go: Erstellen Sie zwei Structs, die die Berichtserstellung unterschiedlich handhaben:- Ein
Metadata-Struct mit den FeldernAuthorundDate— dies repräsentiert öffentliche Informationen, die direkt zugänglich sein sollten. - Ein
Report-Struct mit einemTitle-Feld, dasMetadataeinbettet (für den direkten Zugriff auf Autor und Datum), aber Aggregation für denFormatterverwendet (als internes Implementierungsdetail mit einem benannten Feldformatter).
ReporteineGenerate() string-Methode hinzu, die den internen Formatter verwendet, um den Titel zu formatieren, und die Metadaten einbezieht.- Ein
main.go: Lesen Sie die Berichtsdetails aus der Eingabe ein, erstellen Sie einenReportmit sowohl eingebetteten Metadaten als auch einem aggregierten Formatter. Demonstrieren Sie den Unterschied, indem Sie direkt über den Bericht auf den Autor zugreifen (durch Einbettung) und dann den formatierten Bericht generieren (der den versteckten Formatter intern verwendet).
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Berichtstitel
- Zeile 2: Name des Autors
- Zeile 3: Datum
- Zeile 4: Formatter-Stil
Ihre Format-Methode sollte Folgendes zurückgeben:
[Style] text [/Style]Ihre Generate-Methode sollte Folgendes zurückgeben:
[formatted title]
Author: [Author], Date: [Date]Geben Sie in Ihrer Hauptdatei drei Zeilen aus:
- Den Autor, auf den direkt über den Report zugegriffen wird (was zeigt, dass die Einbettung das Feld befördert)
- Das Datum, auf das über den Namen des eingebetteten Typs zugegriffen wird (
report.Metadata.Date) - Das Ergebnis des Aufrufs von
Generate()
Zum Beispiel, gegeben Quarterly Results, Jane Doe, 2024-01-15 und BOLD, sollte Ihre Ausgabe wie folgt aussehen:
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15Beachten Sie, wie Author und Date durch Einbettung direkt über den Report zugänglich sind, während der Formatter verborgen bleibt — Sie können report.Format() nicht direkt aufrufen, da er mit einem benannten Feld aggregiert ist. Dies demonstriert die "is-a"- gegenüber der "has-a"-Beziehung in der Praxis.
Spickzettel
Go unterstützt zwei Möglichkeiten, ein Struct in ein anderes einzubinden: Embedding (Einbetten) und Aggregation.
Embedding lässt den Feldnamen weg und befördert (promoted) die Felder und Methoden des inneren Typs in das äußere Struct:
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // Embedding - kein Feldname
}
d := Document{}
d.Write() // direkter Zugriff auf eingebettete Methode
Aggregation verwendet einen expliziten Feldnamen, der für den Zugriff auf den inneren Typ erforderlich ist:
type Document struct {
writer Writer // Aggregation - benanntes Feld
}
d := Document{writer: Writer{}}
d.writer.Write() // Feldname muss verwendet werden
Wann man was verwendet:
- Embedding: Drückt eine „is-a“-Beziehung (ist ein) aus. Verwenden Sie dies, wenn der äußere Typ Interfaces des inneren Typs erfüllen soll oder wenn der direkte Methodenzugriff die API verbessert.
- Aggregation: Drückt eine „has-a“-Beziehung (hat ein) aus. Verwenden Sie dies, wenn der innere Typ ein Implementierungsdetail ist, das nicht offengelegt werden soll, oder wenn Sie eine explizite Kontrolle über den Zugriff wünschen.
Beim Embedding können Sie bei Bedarf weiterhin über den Namen des eingebetteten Typs auf Felder zugreifen:
type Metadata struct {
Author string
}
type Report struct {
Metadata // eingebettet
}
r := Report{Metadata: Metadata{Author: "Jane"}}
r.Author // direkter Zugriff via Promotion
r.Metadata.Author // expliziter Zugriff über Typnamen
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 ein Metadata-Struct mit author und date
// TODO: Erstelle einen Formatter mit dem angegebenen style
// TODO: Erstelle einen Report, der Metadata einbettet und Formatter aggregiert
// TODO: Gib den author aus, auf den direkt über den Report zugegriffen wird (Embedding macht das Feld verfügbar)
// TODO: Gib das date aus, auf das über den Namen des eingebetteten Typs zugegriffen wird (report.Metadata.Date)
// TODO: Gib das Ergebnis des Aufrufs von Generate() aus
// Warnungen über ungenutzte Variablen unterdrücken (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 Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool