Composite-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 96 von 107.
Das Composite-Muster ermöglicht es Ihnen, einzelne Objekte und Gruppen von Objekten einheitlich zu behandeln. Während State das Verhalten basierend auf dem internen Zustand ändert, baut Composite Baumstrukturen auf, in denen sowohl Blätter als auch Container dieselbe Schnittstelle teilen.
Dieses Muster eignet sich hervorragend für hierarchische Strukturen wie Dateisysteme, Organigramme oder UI-Komponenten. In Go definieren wir eine gemeinsame Schnittstelle, die sowohl einzelne Elemente als auch Container implementieren:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}Das Kompositum (Container) enthält Kindelemente und implementiert dieselbe Schnittstelle, indem es deren Ergebnisse aggregiert:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Jetzt können Sie Ordner in Ordnern verschachteln, und der Aufruf von GetSize() funktioniert identisch, egal ob es sich um eine einzelne Datei oder einen gesamten Verzeichnisbaum handelt:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Composite ist ideal, wenn Sie Teil-Ganzes-Hierarchien darstellen müssen und möchten, dass Clients Einzelobjekte und Kompositionen identisch behandeln.
Aufgabe
EinfachLassen Sie uns ein Organigramm-System mit dem Composite-Pattern erstellen! Sie werden eine Struktur aufbauen, in der sowohl einzelne Mitarbeiter als auch Abteilungen (die Mitarbeiter oder andere Abteilungen enthalten) einheitlich behandelt werden können – ideal für die Berechnung der Gesamtgehälter in jedem Teil der Organisation.
Sie werden Ihren Code über drei Dateien organisieren:
component.go: Definieren Sie das gemeinsame Interface, das sowohl Einzelpersonen als auch Gruppen implementieren werden.Erstellen Sie ein
OrgComponent-Interface mit zwei Methoden:GetSalary() int— gibt das Gesamtgehalt für diese Komponente zurückGetName() string— gibt den Namen des Mitarbeiters oder der Abteilung zurück
organization.go: Implementieren Sie sowohl den Blatt-Typ (einzelner Mitarbeiter) als auch den Composite-Typ (Abteilung).Erstellen Sie ein
Employee-Struct mit den FeldernName(string) undSalary(int). Die MethodeGetSalary()gibt das eigene Gehalt zurück, undGetName()gibt den Namen zurück.Erstellen Sie ein
Department-Struct mit einemName(string) Feld und einemMembers-Slice, dasOrgComponent-Elemente enthält. Fügen Sie eineAdd(c OrgComponent)-Methode hinzu, um Mitglieder hinzuzufügen. Die MethodeGetSalary()sollte die Summe der Gehälter aller Mitglieder zurückgeben, undGetName()gibt den Namen der Abteilung zurück.main.go: Bauen Sie eine Organisationsstruktur auf und berechnen Sie Gehälter.Lesen Sie die Anzahl der Mitarbeiter ein. Lesen Sie für jeden Mitarbeiter dessen Namen und Gehalt ein und erstellen Sie
Employee-Instanzen. Lesen Sie dann die Anzahl der Abteilungen ein. Lesen Sie für jede Abteilung deren Namen und die Anzahl der Mitglieder-Indizes ein, gefolgt von diesen Indizes (0-basiert, bezogen auf zuvor erstellte Mitarbeiter oder Abteilungen in der Reihenfolge ihrer Erstellung).Nachdem Sie die Struktur aufgebaut haben, lesen Sie einen finalen Index ein und geben Sie den Namen dieser Komponente und das Gesamtgehalt im Format
[name]: [salary]aus.
Die folgenden Eingaben werden bereitgestellt:
- Anzahl der Mitarbeiter, dann der Name und das Gehalt jedes Mitarbeiters in separaten Zeilen
- Anzahl der Abteilungen, dann der Name jeder Abteilung, die Anzahl der Mitglieder und die Mitglieder-Indizes
- Finaler Index zur Abfrage
Zum Beispiel, gegeben:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3Ihre Ausgabe sollte sein:
Engineering: 165000Und gegeben:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Ihre Ausgabe sollte sein:
QA: 125000Und gegeben:
2
Alice
50000
Bob
60000
0
1Ihre Ausgabe sollte sein:
Bob: 60000Beachten Sie, wie GetSalary() identisch funktioniert, egal ob Sie es für einen einzelnen Mitarbeiter oder eine ganze Abteilung aufrufen – das Composite-Pattern ermöglicht es Ihnen, Einzelpersonen und Gruppen einheitlich über dasselbe Interface zu behandeln!
Spickzettel
Das Composite-Muster (Kompositum) behandelt Einzelobjekte und Gruppen von Objekten einheitlich über eine gemeinsame Schnittstelle. Es ist ideal für hierarchische Strukturen wie Dateisysteme, Organigramme oder UI-Komponenten.
Definieren Sie eine gemeinsame Schnittstelle, die sowohl einzelne Elemente als auch Container implementieren:
type Component interface {
GetSize() int
}Implementieren Sie das Blatt (Einzelobjekt):
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}Implementieren Sie das Kompositum (Container), das Kindelemente enthält und deren Ergebnisse aggregiert:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Anwendungsbeispiel, das die einheitliche Behandlung von Einzelobjekten und Kompositionen zeigt:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Probier es selbst
package main
import "fmt"
func main() {
// Anzahl der Mitarbeiter einlesen
var numEmployees int
fmt.Scanln(&numEmployees)
// Alle Komponenten (Mitarbeiter und Abteilungen) in der Reihenfolge ihrer Erstellung speichern
var components []OrgComponent
// TODO: Name und Gehalt jedes Mitarbeiters einlesen
// Employee-Instanzen erstellen und zum Slice components hinzufügen
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: Mitarbeiter erstellen und zu components hinzufügen
}
// Anzahl der Abteilungen einlesen
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: Name, Mitgliederanzahl und Mitglieder-Indizes jeder Abteilung einlesen
// Department-Instanzen erstellen, Mitglieder nach Index hinzufügen und zu components hinzufügen
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: Abteilung erstellen, Mitglieder-Indizes einlesen, Mitglieder hinzufügen, zu components hinzufügen
}
// Den finalen abzufragenden Index einlesen
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: Name der Komponente und Gesamtgehalt im Format [name]: [salary] ausgeben
// Beispiel: fmt.Printf("%s: %d\n", name, salary)
}
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-Parser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer Interfaceencoding/json mit Structshttp.Handler InterfaceZusammenfassung - REST-API-Modelle14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterMiddleware als Decorator3Zeiger & 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