Menu
Coddy logo textTech

Wzorzec kompozytu

Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 96 z 107.

Wzorzec Composite pozwala traktować pojedyncze obiekty i grupy obiektów w jednolity sposób. Podczas gdy wzorzec State zmienia zachowanie w zależności od stanu wewnętrznego, wzorzec Composite buduje struktury drzewiaste, w których zarówno liście, jak i kontenery współdzielą ten sam interfejs.

Ten wzorzec doskonale sprawdza się w przypadku struktur hierarchicznych, takich jak systemy plików, schematy organizacyjne czy komponenty interfejsu użytkownika. W Go definiujemy wspólny interfejs, który implementują zarówno poszczególne elementy, jak i kontenery:

type Component interface {
    GetSize() int
}

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

Kompozyt (kontener) zawiera elementy podrzędne i implementuje ten sam interfejs, agregując ich wyniki:

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
}

Teraz możesz zagnieżdżać foldery w folderach, a wywołanie GetSize() działa identycznie, niezależnie od tego, czy chodzi o pojedynczy plik, czy całe drzewo katalogów:

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

Wzorzec Composite sprawdza się idealnie, gdy chcesz przedstawiać hierarchie część–całość i zależy ci na tym, aby klienci traktowali pojedyncze obiekty i kompozycje identycznie.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system schematu organizacyjnego z użyciem wzorca Composite! Utworzysz strukturę, w której zarówno poszczególnych pracowników, jak i działy (zawierające pracowników lub inne działy) można traktować jednolicie — idealne rozwiązanie do obliczania łącznych wynagrodzeń w dowolnej części organizacji.

Rozmieścisz kod w trzech plikach:

  • component.go: Zdefiniuj wspólny interfejs, który będą implementować zarówno pojedyncze osoby, jak i grupy.

    Utwórz interfejs OrgComponent z dwiema metodami:

    • GetSalary() int: zwraca łączne wynagrodzenie tego komponentu
    • GetName() string: zwraca imię i nazwisko pracownika lub nazwę działu
  • organization.go: Zaimplementuj typy liścia (pojedynczego pracownika) i kompozytu (działu).

    Utwórz strukturę Employee z polami Name (string) i Salary (int). Jej metoda GetSalary() zwraca wynagrodzenie pracownika, a GetName() zwraca jego imię i nazwisko.

    Utwórz strukturę Department z polem Name (string) i wycinkiem Members, który przechowuje elementy typu OrgComponent. Dodaj metodę Add(c OrgComponent), aby dodawać członków. Metoda GetSalary() powinna zwracać sumę wynagrodzeń wszystkich członków, a GetName() — nazwę działu.

  • main.go: Zbuduj strukturę organizacji i oblicz wynagrodzenia.

    Wczytaj liczbę pracowników. W przypadku każdego pracownika wczytaj jego imię i nazwisko oraz wynagrodzenie, tworząc instancję Employee. Następnie wczytaj liczbę działów. W przypadku każdego działu wczytaj jego nazwę i liczbę indeksów członków, a następnie te indeksy (liczone od 0, odnoszące się do wcześniej utworzonych pracowników lub działów w kolejności ich utworzenia).

    Po zbudowaniu struktury wczytaj końcowy indeks i wypisz nazwę tego komponentu oraz łączne wynagrodzenie w formacie: [name]: [salary]

Dane wejściowe będą podane w następującej kolejności:

  • Liczba pracowników, a następnie imię i nazwisko oraz wynagrodzenie każdego pracownika w osobnych wierszach
  • Liczba działów, a następnie nazwa każdego działu, liczba jego członków oraz indeksy członków
  • Końcowy indeks do sprawdzenia

Na przykład dla danych:

3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3

Wynik powinien być następujący:

Engineering: 165000

A dla danych:

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

Wynik powinien być następujący:

QA: 125000

A dla danych:

2
Alice
50000
Bob
60000
0
1

Wynik powinien być następujący:

Bob: 60000

Zwróć uwagę, że GetSalary() działa identycznie niezależnie od tego, czy wywołujesz ją dla pojedynczego pracownika, czy całego działu: wzorzec Composite pozwala traktować pojedyncze osoby i grupy jednolicie za pomocą tego samego interfejsu!

Spróbuj swoich sił

package main

import "fmt"

func main() {
	// Wczytaj liczbę pracowników
	var numEmployees int
	fmt.Scanln(&numEmployees)

	// Przechowuj wszystkie komponenty (pracowników i działy) w kolejności ich tworzenia
	var components []OrgComponent

	// TODO: Wczytaj imię i wynagrodzenie każdego pracownika
	// Utwórz instancje Employee i dodaj je do slice components
	for i := 0; i < numEmployees; i++ {
		var name string
		var salary int
		fmt.Scanln(&name)
		fmt.Scanln(&salary)
		// TODO: Utwórz pracownika i dodaj go do components
	}

	// Wczytaj liczbę działów
	var numDepartments int
	fmt.Scanln(&numDepartments)

	// TODO: Wczytaj nazwę każdego działu, liczbę członków i ich indeksy
	// Utwórz instancje Department, dodaj członków według indeksów i dodaj instancje do components
	for i := 0; i < numDepartments; i++ {
		var deptName string
		var memberCount int
		fmt.Scanln(&deptName)
		fmt.Scanln(&memberCount)
		// TODO: Utwórz dział, wczytaj indeksy członków, dodaj członków i dodaj dział do components
	}

	// Wczytaj końcowy indeks do wyszukania
	var queryIndex int
	fmt.Scanln(&queryIndex)

	// TODO: Wypisz nazwę komponentu i łączne wynagrodzenie w formacie: [name]: [salary]
	// Przykład: fmt.Printf("%s: %d\n", name, salary)
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online