Menu
Coddy logo textTech

Embedding e aggregazione a confronto

Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 38 di 107.

Hai imparato che l'incorporamento promuove i campi e i metodi nella struct esterna. Ma Go supporta anche l'aggregazione, in cui includi un'altra struct come campo con nome. Capire quando usare ciascun approccio è fondamentale per una buona progettazione.

Con l’embedding, ometti il nome del campo e i membri del tipo interno diventano direttamente accessibili:

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // incrustación - sin nombre de campo
}

d := Document{}
d.Write()  // acceso directo

Con l'aggregazione, assegni al campo un nome esplicito, che dovrai usare per accedere al tipo interno:

type Document struct {
    writer Writer  // aggregazione - campo con nome
}

d := Document{writer: Writer{}}
d.writer.Write()  // è necessario usare il nome del campo

La differenza fondamentale riguarda la relazione che stai esprimendo. L'ereditarietà suggerisce una relazione «è un», in cui il tipo esterno assume l'identità del tipo interno. L'aggregazione suggerisce una relazione «ha un», in cui il tipo esterno si limita a contenere il tipo interno.

Usa l'incorporamento quando vuoi che il tipo esterno soddisfi le interfacce implementate dal tipo interno, oppure quando l'accesso diretto ai metodi rende l'API più semplice. Usa l'aggregazione quando il tipo interno è un dettaglio di implementazione che non dovrebbe essere esposto, oppure quando vuoi un controllo esplicito su come accedere al tipo interno.

challenge icon

Sfida

Facile

Costruiamo un sistema di generazione di report che mostri quando usare l’incorporamento invece dell’aggregazione. Creerai un sistema in cui alcuni componenti sono accessibili direttamente (incorporamento), mentre altri rimangono dettagli interni dell’implementazione (aggregazione).

Organizzerai il codice in tre file:

  • formatter.go: crea una struct Formatter con un campo Style (string) e un metodo Format(text string) string che restituisce il testo racchiuso nello stile, come [Style] text [/Style]. Sarà un helper interno che non dovrebbe essere esposto direttamente.
  • report.go: crea due struct che gestiscono la generazione dei report in modo diverso:
    • Una struct Metadata con i campi Author e Date. Rappresenta informazioni pubbliche che dovrebbero essere accessibili direttamente
    • Una struct Report con un campo Title che incorpora Metadata (per accedere direttamente all’autore e alla data), ma usa l’aggregazione per Formatter (come dettaglio interno dell’implementazione, con un campo denominato formatter)
    Aggiungi alla struct Report un metodo Generate() string che usa il formatter interno per formattare il titolo e includere i metadati.
  • main.go: leggi i dettagli del report dall’input e crea un Report con metadati incorporati e un formatter aggregato. Mostra la differenza accedendo direttamente all’autore tramite il report (con l’incorporamento) e poi generando il report formattato (che usa internamente il formatter nascosto).

Verranno forniti i seguenti input:

  • Riga 1: titolo del report
  • Riga 2: nome dell’autore
  • Riga 3: data
  • Riga 4: stile del formatter

Il tuo metodo Format dovrebbe restituire:

[Style] text [/Style]

Il tuo metodo Generate dovrebbe restituire:

[formatted title]
Author: [Author], Date: [Date]

Nel file principale, stampa tre righe:

  1. L’autore a cui si accede direttamente dal Report (per mostrare che l’incorporamento promuove il campo)
  2. La data a cui si accede tramite il nome del tipo incorporato (report.Metadata.Date)
  3. Il risultato della chiamata a Generate()

Per esempio, con Quarterly Results, Jane Doe, 2024-01-15 e BOLD, l’output dovrebbe essere:

Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15

Nota che è possibile accedere direttamente a Author e Date dal Report tramite l’incorporamento, mentre il formatter rimane nascosto. Non puoi chiamare direttamente report.Format(), perché è aggregato con un campo denominato. Questo mostra nella pratica la relazione «è-un» rispetto a «ha-un».

Provalo tu

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	scanner.Scan()
	title := scanner.Text()
	
	scanner.Scan()
	author := scanner.Text()
	
	scanner.Scan()
	date := scanner.Text()
	
	scanner.Scan()
	style := scanner.Text()
	
	// TODO: Crea una struct Metadata con l'autore e la data
	
	// TODO: Crea un Formatter con lo stile indicato
	
	// TODO: Crea un Report che incorpori Metadata e aggreghi Formatter
	
	// TODO: Stampa l'autore accedendovi direttamente tramite Report (l'incorporamento promuove il campo)
	
	// TODO: Stampa la data accedendovi tramite il nome del tipo incorporato (report.Metadata.Date)
	
	// TODO: Stampa il risultato della chiamata a Generate()
	
	// Evita gli avvisi sulle variabili inutilizzate (rimuovi queste righe quando usi le variabili)
	_ = title
	_ = author
	_ = date
	_ = style
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore Go online