Composite-Muster
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 96 von 107.
Das Composite-Muster ermöglicht es dir, einzelne Objekte und Objektgruppen einheitlich zu behandeln. Während State das Verhalten auf Grundlage des internen Zustands ändert, erstellt Composite Baumstrukturen, in denen sowohl Blätter als auch Container dieselbe Schnittstelle verwenden.
Dieses Muster eignet sich perfekt 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 zusammengesetzte Objekt (der Container) enthält untergeordnete Objekte 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 kannst du Ordner in Ordnern verschachteln, und der Aufruf von GetSize() funktioniert identisch, unabhängig davon, 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 du Teil-Ganzes-Hierarchien darstellen musst und möchtest, dass Clients einzelne Objekte und Kompositionen identisch behandeln.
Aufgabe
EinfachErstellen wir ein Organigramm-System mithilfe des Composite-Musters! Du wirst eine Struktur erstellen, in der sowohl einzelne Mitarbeiter als auch Abteilungen (die Mitarbeiter oder andere Abteilungen enthalten) einheitlich behandelt werden können – perfekt, um die Gesamtgehälter für jeden Teil der Organisation zu berechnen.
Du wirst deinen Code auf drei Dateien verteilen:
component.go: Definiere die gemeinsame Schnittstelle, die sowohl einzelne Mitarbeiter als auch Gruppen implementieren werden.Erstelle eine
OrgComponent-Schnittstelle mit zwei Methoden:GetSalary() int: gibt das Gesamtgehalt dieser Komponente zurückGetName() string: gibt den Namen des Mitarbeiters oder der Abteilung zurück
organization.go: Implementiere sowohl den Blatt-Typ (einzelner Mitarbeiter) als auch den Komposit-Typ (Abteilung).Erstelle eine
Employee-Struktur mit den FeldernName(string) undSalary(int). Die MethodeGetSalary()gibt das eigene Gehalt zurück, undGetName()gibt den eigenen Namen zurück.Erstelle eine
Department-Struktur mit einemName-Feld (string) und einemMembers-Slice, derOrgComponent-Elemente enthält. Füge eineAdd(c OrgComponent)-Methode hinzu, um Mitglieder hinzuzufügen. Die MethodeGetSalary()soll die Summe der Gehälter aller Mitglieder zurückgeben, undGetName()gibt den Namen der Abteilung zurück.main.go: Erstelle eine Organisationsstruktur und berechne die Gehälter.Lies die Anzahl der Mitarbeiter ein. Lies für jeden Mitarbeiter seinen Namen und sein Gehalt ein und erstelle
Employee-Instanzen. Lies anschließend die Anzahl der Abteilungen ein. Lies für jede Abteilung ihren Namen und die Anzahl der Mitgliederindizes ein, gefolgt von diesen Indizes (0-basiert; sie beziehen sich in der Reihenfolge ihrer Erstellung auf zuvor erstellte Mitarbeiter oder Abteilungen).Lies nach dem Erstellen der Struktur einen abschließenden Index ein und gib den Namen und das Gesamtgehalt dieser Komponente im Format
[name]: [salary]aus.
Die folgenden Eingaben werden bereitgestellt:
- Anzahl der Mitarbeiter, anschließend der Name und das Gehalt jedes Mitarbeiters in separaten Zeilen
- Anzahl der Abteilungen, anschließend der Name jeder Abteilung, die Anzahl ihrer Mitglieder und die Mitgliederindizes
- Abschließender Index für die Abfrage
Zum Beispiel bei:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3Deine Ausgabe sollte sein:
Engineering: 165000Und bei:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Deine Ausgabe sollte sein:
QA: 125000Und bei:
2
Alice
50000
Bob
60000
0
1Deine Ausgabe sollte sein:
Bob: 60000Beachte, wie GetSalary() identisch funktioniert, unabhängig davon, ob du die Methode für einen einzelnen Mitarbeiter oder eine gesamte Abteilung aufrufst: Das Composite-Muster ermöglicht es dir, einzelne Elemente und Gruppen über dieselbe Schnittstelle einheitlich zu behandeln!
Probier es selbst
package main
import "fmt"
func main() {
// Lies die Anzahl der Mitarbeiter
var numEmployees int
fmt.Scanln(&numEmployees)
// Speichere alle Komponenten (Mitarbeiter und Abteilungen) in der Reihenfolge der Erstellung
var components []OrgComponent
// TODO: Lies den Namen und das Gehalt jedes Mitarbeiters
// Erstelle Employee-Instanzen und füge sie dem components-Slice hinzu
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: Erstelle employee und füge zu components hinzu
}
// Lies die Anzahl der Abteilungen
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: Lies den Namen, die Mitgliederanzahl und die Mitgliederindizes jeder Abteilung
// Erstelle Department-Instanzen, füge Mitglieder nach Index hinzu und füge zu components hinzu
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: Erstelle department, lies Mitgliederindizes, füge Mitglieder hinzu, füge zu components hinzu
}
// Lies den finalen Index für die Abfrage
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: Gib den Namen und das Gesamtgehalt der Komponente im Format aus: [name]: [salary]
// 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 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 – Dateiparser11Standardbibliothek & OOP
io.Reader & io.Writersort.Interfacefmt.Stringer-Interfaceencoding/json mit Structshttp.Handler-InterfaceRückblick – REST-API-Modelle14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Methoden-MusterState-MusterComposite-MusterMiddleware als Decorator3Zeiger & 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