Menu
Coddy logo textTech

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())  // 175

Composite ist ideal, wenn Sie Teil-Ganzes-Hierarchien darstellen müssen und möchten, dass Clients Einzelobjekte und Kompositionen identisch behandeln.

challenge icon

Aufgabe

Einfach

Lassen 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ück
    • GetName() 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 Feldern Name (string) und Salary (int). Die Methode GetSalary() gibt das eigene Gehalt zurück, und GetName() gibt den Namen zurück.

    Erstellen Sie ein Department-Struct mit einem Name (string) Feld und einem Members-Slice, das OrgComponent-Elemente enthält. Fügen Sie eine Add(c OrgComponent)-Methode hinzu, um Mitglieder hinzuzufügen. Die Methode GetSalary() sollte die Summe der Gehälter aller Mitglieder zurückgeben, und GetName() 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
3

Ihre Ausgabe sollte sein:

Engineering: 165000

Und gegeben:

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

Ihre Ausgabe sollte sein:

QA: 125000

Und gegeben:

2
Alice
50000
Bob
60000
0
1

Ihre Ausgabe sollte sein:

Bob: 60000

Beachten 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())  // 175

Probier 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)
}
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