Lezioni del modulo (3/5)
Composizione (embedding)
Go non ha ereditarietà. La risposta idiomatica al riuso è la composizione, e in particolare l'embedding: includere un tipo dentro un altro senza dargli un nome di campo. I campi e i metodi del tipo incorporato vengono promossi, diventando direttamente accessibili come se appartenessero al tipo esterno.
Embedding di struct
type Animal struct {
Name string
}
func (a Animal) Greet() string {
return "ciao da " + a.Name
}
type Dog struct {
Animal // embedding — NESSUN nome di campo
Breed string
}
c := Dog{
Animal: Animal{Name: "Rex"},
Breed: "Pastore",
}
fmt.Println(c.Name) // "Rex" — promosso da Animal
fmt.Println(c.Greet()) // "ciao da Rex" — metodo promosso
fmt.Println(c.Breed) // campo proprioIl campo embedded è comunque accessibile col nome del tipo:
c.Animal.Name funziona ed è identico a c.Name. Serve quando ci
sono collisioni.
Override (parziale): "shadowing" di metodi
Se il tipo esterno definisce un metodo con lo stesso nome di quello promosso, il suo metodo "vince". Da dentro puoi sempre raggiungere quello embedded col nome del tipo:
func (c Dog) Greet() string {
return "BAU! " + c.Animal.Greet()
}Non è ereditarietà polimorfica: è solo risoluzione lessicale dei nomi. Per il polimorfismo si usano le interfacce (Modulo 6).
Embedding multiplo
Puoi incorporare più tipi:
type Logger struct{ /*...*/ }
func (l Logger) Log(msg string) { /*...*/ }
type Timer struct{ /*...*/ }
func (t Timer) Now() time.Time { /*...*/ }
type Service struct {
Logger
Timer
Name string
}
s := Service{Name: "api"}
s.Log("start") // promosso da Logger
s.Now() // promosso da TimerSe due tipi incorporati hanno un metodo con LO STESSO nome, l'accesso
diretto diventa ambiguo e il compilatore richiede la forma esplicita
(s.Logger.Foo() o s.Timer.Foo()).
Embedding di interfacce
Anche le interfacce si possono "embeddare" (vedi Modulo 6):
type ReadWriter interface {
io.Reader
io.Writer
}Equivale all'unione dei metodi di Reader e Writer.
Quando usare la composizione esplicita (non embedding)
Se vuoi un campo nominato e non l'auto-promozione, usa la normale composizione:
type Dog struct {
Anim Animal // campo NORMALE, niente promozione
Breed string
}
c.Anim.Name // serve il nome del campoL'embedding è uno strumento di delega comodo, non una scelta di design migliore della composizione esplicita: usalo quando vuoi davvero promuovere l'API del tipo interno.
Prova tu
Definisci Dog che incorpora Animal (embedding, senza nome di campo) e ha un campo Breed string.
Mostra suggerimento
Embedding: scrivi il TIPO senza dargli un nome di campo.
Soluzione disponibile dopo 3 tentativi
Aggiungi ad Animal un metodo Greet() string e chiamalo direttamente su una variabile di tipo Dog (promozione del metodo).
Mostra suggerimento
Il metodo definito su Animal viene promosso a Dog grazie all'embedding.
Soluzione disponibile dopo 3 tentativi
Go supporta l'ereditarietà classica?
type B struct { /* extends A? */ }Recap
- Niente ereditarietà: si compone con embedding.
- Embedding = scrivere il TIPO senza nome di campo nella struct.
- Campi e metodi del tipo incorporato vengono promossi.
- Accesso esplicito sempre disponibile:
outer.Inner.Campo. - Override "lessicale": il metodo del tipo esterno copre quello promosso.
- Embedding ≠ is-a: per polimorfismo usa le interfacce.