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:

  1. Pozwala jasno zdefiniować hierarchiczne złożone obiekty, reprezentując wszystkie lub część hierarchii obiektów, ułatwiając dodawanie nowych składników.
  2. 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

  1. Gdy chcesz reprezentować hierarchię „całość-część” obiektów.
  2. 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

Wzorzec kompozytowy w języku Golang

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
}