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.
Sfida
FacileCostruiamo 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 structFormattercon un campoStyle(string) e un metodoFormat(text string) stringche 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
Metadatacon i campiAuthoreDate. Rappresenta informazioni pubbliche che dovrebbero essere accessibili direttamente - Una struct
Reportcon un campoTitleche incorporaMetadata(per accedere direttamente all’autore e alla data), ma usa l’aggregazione perFormatter(come dettaglio interno dell’implementazione, con un campo denominatoformatter)
Reportun metodoGenerate() stringche usa il formatter interno per formattare il titolo e includere i metadati.- Una struct
main.go: leggi i dettagli del report dall’input e crea unReportcon 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:
- L’autore a cui si accede direttamente dal Report (per mostrare che l’incorporamento promuove il campo)
- La data a cui si accede tramite il nome del tipo incorporato (
report.Metadata.Date) - 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-15Nota 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
}
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
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online