1. Cos'è il pattern composito

Il Pattern Composito è un pattern di progettazione strutturale degli oggetti comunemente utilizzato. Combina gli oggetti in una struttura ad albero per rappresentare una relazione gerarchica "parte-tutto", consentendo ai clienti di gestire singoli oggetti e composizioni di oggetti in modo coerente.

2. Caratteristiche e vantaggi del pattern composito

I principali vantaggi del pattern composito sono:

  1. Può definire chiaramente gli oggetti complessi gerarchici, rappresentando tutta o parte della gerarchia degli oggetti, semplificando l'aggiunta di nuovi componenti.
  2. Fornisce un'interfaccia unificata, rendendo l'accesso alle parti composite e agli oggetti singoli coerente, consentendo ai clienti di utilizzare uniformemente gli oggetti singoli e quelli compositi.

3. Scenari di applicazione del pattern composito

  1. Quando si desidera rappresentare la gerarchia "parte-tutto" degli oggetti.
  2. Quando si desidera che i client ignorino le differenze tra gli oggetti compositi e gli oggetti singoli, e utilizzino uniformemente tutti gli oggetti nella struttura composita.

4. Implementazione del pattern composito in Golang

Supponiamo di dover progettare un’applicazione di e-commerce, il catalogo prodotti è un buon esempio del pattern composito. Una categoria può contenere altre categorie e anche prodotti (ad esempio la categoria elettronica contiene telefoni, computer, e i telefoni contengono iPhone, Samsung, ecc.).

4.1 Diagramma delle Classi UML

Pattern Composito in Golang

4.2 Introduzione dell'esempio

In questo esempio, abbiamo Component (ovviamente utilizzando la caratteristica dell'interfaccia dell'orientamento agli oggetti) come classe base astratta e sia Composite che Leaf implementano questa interfaccia, rappresentando oggetti contenitore e oggetti base, rispettivamente.

4.3 Passo 1 dell'implementazione: Definire la classe component astratta

4.3.1 Definire l'interfaccia della classe componente astratta

// Component: Interfaccia del componente di base, definisce la comunalità dei gruppi e degli individui
type Component interface {
    Cerca(string)
}

4.3.2 Implementare i metodi di base della classe componente astratta

Questo passaggio è implementato specificamente nella classe componente contenitore e nella classe componente foglia.

4.4 Passo 2 dell'implementazione: Definire la classe componente foglia

Questa classe concreta rappresenta la categoria o l'oggetto di fondo nella gerarchia e non ha la prossima struttura di oggetti.

4.4.1 Ereditare dalla classe componente astratta

In Go, l'ereditarietà dell'interfaccia è implementata dal modo in cui i metodi sono implementati attraverso Struct.

4.4.2 Implementare i metodi specifici della classe componente foglia

// Prodotto: Rappresenta un nodo foglia, cioè un prodotto, e non può avere nodi figlio
type Prodotto struct {
    Nome string
}

// Cerca: Cerca i prodotti
func (p *Prodotto) Cerca(parolaChiave string) {
    if strings.Contains(p.Nome, parolaChiave) {
        fmt.Printf("Prodotto: '%s' contiene la parola chiave: '%s'\n", p.Nome, parolaChiave)
    }
}

4.5 Passo 3 dell'implementazione: Definire la classe componente contenitore

Questa classe è utilizzata per memorizzare e gestire gli oggetti figlio e include generalmente alcuni metodi per gestire e organizzare gli oggetti figlio, come aggiungi(Componente), rimuovi(Componente), ecc.

4.5.1 Eredità dalla classe componente astratta

Anche questo è implementato utilizzando Struct per implementare i metodi dell'interfaccia in Go.

4.5.2 Implementazione dei metodi specifici della classe componente contenitore

// Categoria: Rappresenta un nodo contenitore, cioè una categoria di prodotti, che può avere nodi figlio
type Categoria struct {
    Nome     string
    Figli []Component
}

// Aggiungi: Aggiunge un nodo figlio
func (c *Categoria) Aggiungi(figlio Component) {
    c.Figli = append(c.Figli, figlio)
}

// Rimuovi: Rimuove un nodo figlio
func (c *Categoria) Rimuovi(figlio Component) {
    // Implementazione specifica omessa
}

// Cerca: Cerca i prodotti
func (c *Categoria) Cerca(parolaChiave string) {
    fmt.Printf("Categoria: %s\n", c.Nome)
    for _, composito := range c.Figli {
        composito.Cerca(parolaChiave)
    }
}

4.6 Implementazione Passo 4: Esempio di Codice Client

Istanzia una struttura, assemblala in una struttura ad albero, e quindi chiama l'operazione di accesso della struttura ad albero.

func main() {
    root := &Category{Name: "Radice"}
    elettronica := &Category{Name: "Elettronica"}

    telefono := &Product{Name: "Telefono"}
    tv := &Product{Name: "Televisione"}

    root.Add(elettronica)
    elettronica.Add(telefono)
    elettronica.Add(tv)

    root.Search("telefono") // Questo controllerà in tutti i figli
}