1. Co to jest wzorzec kompozytowy
Wzorzec kompozytowy to powszechnie stosowany wzorzec projektowy struktury obiektów. Łączy obiekty w strukturę drzewa, aby reprezentować hierarchiczną relację „całość-część”, umożliwiając klientom operowanie na poszczególnych obiektach i kompozycjach obiektów w spójny sposób.
2. Charakterystyka i zalety wzorca kompozytowego
Główne zalety wzorca kompozytowego to:
- Pozwala jasno zdefiniować hierarchiczne złożone obiekty, reprezentując wszystkie lub część hierarchii obiektów, ułatwiając dodawanie nowych składników.
- Zapewnia znormalizowany interfejs, umożliwiając jednolite dostęp do składowych i indywidualnych obiektów, umożliwiając klientom jednolite korzystanie zarówno ze składników, jak i obiektów składanego.
3. Scenariusze zastosowania wzorca kompozytowego
- Gdy chcesz reprezentować hierarchię „całość-część” obiektów.
- Gdy chcesz, aby klienci zignorowali różnice między obiektami złożonymi i indywidualnymi oraz jednolicie korzystali ze wszystkich obiektów w strukturze kompozytu.
4. Implementacja wzorca kompozytowego w języku Golang
Załóżmy, że projektujemy aplikację e-commerce, katalog produktów jest dobrym przykładem wzorca kompozytowego. Kategoria może zawierać inne kategorie oraz produkty (np. kategoria elektroniki zawiera telefony, komputery, a telefony zawierają iPhony, telefony Samsung, itp.).
4.1 Diagram klas UML
4.2 Przykładowe wprowadzenie
W tym przykładzie mamy „Komponent” (oczywiście wykorzystując cechę interfejsu orientacji obiektowej) jako abstrakcyjną klasę bazową, a „Kompozyt” i „Liść” obie implementują ten interfejs, reprezentując odpowiednio obiekty kontenerowe i podstawowe obiekty.
4.3 Krok implementacji 1: Zdefiniuj abstrakcyjną klasę komponentu
Ta metoda jest zazwyczaj określana w klasie interfejsu i stanowi kluczową centralną operację.
4.3.1 Zdefiniuj interfejs abstrakcyjnej klasy komponentu
//Komponent: Interfejs podstawowego komponentu, definiuje wspólne cechy grup i jednostek
type Komponent interface {
Szukaj(string)
}
4.3.2 Implementuj podstawowe metody abstrakcyjnej klasy komponentu
Ten krok jest w szczególności realizowany w klasie komponentu kontenerowego i klasie komponentu liścia.
4.4 Krok implementacji 2: Zdefiniuj klasę komponentu liścia
Ta konkretne klasa reprezentuje najniższą kategorię lub obiekt w hierarchii i nie ma następnego poziomu obiektów.
4.4.1 Dziedzicz z abstrakcyjnej klasy komponentu
W języku Go, dziedziczenie interfejsu jest realizowane poprzez implementację metod za pomocą struktur.
4.4.2 Implementuj metody specyficzne dla klasy komponentu liścia
//Produkt: Reprezentuje węzeł liścia, czyli produkt, i nie może mieć węzłów potomnych
type Produkt struct {
Nazwa string
}
//Szukaj: Wyszukaj produkty
func (p *Produkt) Szukaj(słowoKluczowe string) {
if strings.Contains(p.Nazwa, słowoKluczowe) {
fmt.Printf("Produkt: '%s' zawiera słowo kluczowe: '%s'\n", p.Nazwa, słowoKluczowe)
}
}
4.5 Krok implementacji 3: Zdefiniuj klasę komponentu kontenera
Ta klasa służy do przechowywania i zarządzania obiektami potomnymi, zazwyczaj zawiera kilka metod do zarządzania i organizowania obiektów potomnych, takich jak Dodaj(Komponent)
, Usuń(Komponent)
, itp.
4.5.1 Dziedzicz z abstrakcyjnej klasy komponentu
To również jest realizowane przy użyciu struktury do implementacji metod interfejsu w języku Go.
4.5.2 Implementowanie metod specyficznych dla klasy komponentu kontenera
// Kategoria: Reprezentuje węzeł kontenera, czyli kategorię produktów, która może mieć węzły potomne
type Kategoria struct {
Nazwa string
Dzieci []Komponent
}
// Dodaj: Dodaj węzeł potomny
func (k *Kategoria) Dodaj(potomek Komponent) {
k.Dzieci = append(k.Dzieci, potomek)
}
// Usuń: Usuń węzeł potomny
func (k *Kategoria) Usuń(potomek Komponent) {
// Konkretna implementacja pominięta
}
// Szukaj: Wyszukaj produkty
func (k *Kategoria) Szukaj(słowoKluczowe string) {
fmt.Printf("Kategoria: %s\n", k.Nazwa)
for _, kompozyt := range k.Dzieci {
kompozyt.Szukaj(słowoKluczowe)
}
}
4.6 Krok implementacji 4: Przykład kodu klienta
Utwórz strukturę, zmontuj ją w strukturę drzewa, a następnie wywołaj operację dostępu do struktury drzewa.
func main() {
root := &Category{Name: "Root"}
electronics := &Category{Name: "Elektronika"}
telefon := &Product{Name: "Telefon"}
telewizor := &Product{Name: "Telewizor"}
root.Add(electronics)
electronics.Add(telefon)
electronics.Add(telewizor)
root.Search("telefon") // To wyszuka we wszystkich dzieciach
}