00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Embedding

Che cosa e l’Embedding?

In Go non esiste il concetto di ereditarieta come in linguaggi orientati agli oggetti tradizionali. Al suo posto, Go offre l’embedding (incorporamento), un meccanismo potente basato sul principio della composizione sopra l’ereditarieta. L’embedding permette di includere un tipo all’interno di un altro, promuovendo automaticamente i suoi campi e metodi.

Struct Embedding

L’embedding di struct si ottiene dichiarando un campo senza un nome esplicito:

package main

import "fmt"

type Indirizzo struct {
    Via    string
    Citta  string
    CAP    string
}

func (i Indirizzo) Completo() string {
    return fmt.Sprintf("%s, %s %s", i.Via, i.Citta, i.CAP)
}

type Persona struct {
    Nome string
    Eta  int
    Indirizzo // Campo embedded (senza nome)
}

func main() {
    p := Persona{
        Nome: "Marco",
        Eta:  30,
        Indirizzo: Indirizzo{
            Via:   "Via Roma 10",
            Citta: "Milano",
            CAP:   "20100",
        },
    }

    // Accesso diretto ai campi dell'Indirizzo
    fmt.Println(p.Via)   // Via Roma 10
    fmt.Println(p.Citta) // Milano

    // Accesso al metodo promosso
    fmt.Println(p.Completo()) // Via Roma 10, Milano 20100

    // Possiamo anche accedere esplicitamente
    fmt.Println(p.Indirizzo.Via) // Via Roma 10
}

Quando embeddiamo Indirizzo in Persona, tutti i campi e i metodi di Indirizzo diventano accessibili direttamente su Persona. Questo si chiama promozione dei metodi.

Promozione dei metodi

I metodi del tipo embedded vengono promossi al tipo contenitore. Questo significa che possiamo chiamarli come se fossero definiti direttamente sul tipo esterno:

package main

import "fmt"

type Motore struct {
    Potenza    int
    Cilindrata float64
}

func (m Motore) Avvia() {
    fmt.Printf("Motore avviato: %d CV, %.1fL\n", m.Potenza, m.Cilindrata)
}

func (m Motore) Spegni() {
    fmt.Println("Motore spento")
}

type Auto struct {
    Marca   string
    Modello string
    Motore  // Embedding
}

func main() {
    auto := Auto{
        Marca:   "Fiat",
        Modello: "500",
        Motore: Motore{
            Potenza:    69,
            Cilindrata: 1.2,
        },
    }

    // Metodi promossi dal Motore
    auto.Avvia()  // Motore avviato: 69 CV, 1.2L
    auto.Spegni() // Motore spento
}

Sovrascrivere metodi promossi

Il tipo esterno puo definire un metodo con lo stesso nome di un metodo promosso. In questo caso, il metodo del tipo esterno ha la precedenza:

package main

import "fmt"

type Base struct{}

func (b Base) Saluta() string {
    return "Ciao dalla Base"
}

type Derivato struct {
    Base
}

// Sovrascriviamo il metodo Saluta
func (d Derivato) Saluta() string {
    return "Ciao dal Derivato"
}

func main() {
    d := Derivato{}
    fmt.Println(d.Saluta())      // Ciao dal Derivato
    fmt.Println(d.Base.Saluta()) // Ciao dalla Base (accesso diretto)
}

Il metodo originale rimane comunque accessibile tramite il nome esplicito del tipo embedded.

Interface Embedding

Anche le interfacce possono essere embedded in altre interfacce, creando interfacce composte:

package main

import "fmt"

type Lettore interface {
    Leggi(p []byte) (n int, err error)
}

type Scrittore interface {
    Scrivi(p []byte) (n int, err error)
}

// Interfaccia composta tramite embedding
type LettoreScrittore interface {
    Lettore
    Scrittore
}

type File struct {
    nome string
    dati []byte
}

func (f *File) Leggi(p []byte) (int, error) {
    n := copy(p, f.dati)
    fmt.Printf("Letti %d byte da %s\n", n, f.nome)
    return n, nil
}

func (f *File) Scrivi(p []byte) (int, error) {
    f.dati = append(f.dati, p...)
    fmt.Printf("Scritti %d byte su %s\n", len(p), f.nome)
    return len(p), nil
}

func elabora(rw LettoreScrittore) {
    rw.Scrivi([]byte("dati"))
    buf := make([]byte, 10)
    rw.Leggi(buf)
}

func main() {
    f := &File{nome: "test.txt"}
    elabora(f)
}

Questo e esattamente il pattern usato nella libreria standard con io.Reader, io.Writer e io.ReadWriter.

Embedding di puntatori

E possibile anche embeddere puntatori a struct:

package main

import "fmt"

type Logger struct {
    Prefisso string
}

func (l *Logger) Log(messaggio string) {
    fmt.Printf("[%s] %s\n", l.Prefisso, messaggio)
}

type Servizio struct {
    *Logger // Embedding di puntatore
    Nome    string
}

func main() {
    logger := &Logger{Prefisso: "INFO"}
    s := Servizio{
        Logger: logger,
        Nome:   "AuthService",
    }

    s.Log("Servizio avviato") // [INFO] Servizio avviato
}

L’embedding di puntatori e utile quando piu struct devono condividere la stessa istanza del tipo embedded.

Embedding vs Ereditarieta

E importante capire le differenze tra l’embedding di Go e l’ereditarieta dei linguaggi OOP:

package main

import "fmt"

type Animale struct {
    Nome string
}

func (a Animale) ChiSei() string {
    return fmt.Sprintf("Sono un animale: %s", a.Nome)
}

type Cane struct {
    Animale
    Razza string
}

func main() {
    c := Cane{
        Animale: Animale{Nome: "Rex"},
        Razza:   "Pastore tedesco",
    }

    // ATTENZIONE: non e polimorfismo!
    // Il metodo ChiSei() usa i campi di Animale, non di Cane
    fmt.Println(c.ChiSei()) // Sono un animale: Rex

    // In Go usiamo le interfacce per il polimorfismo
    var a interface{ ChiSei() string } = c
    fmt.Println(a.ChiSei()) // Sono un animale: Rex
}

Differenze chiave:

  • Non c’e polimorfismo implicito: il metodo promosso non “conosce” il tipo esterno.
  • Non c’e una relazione “e-un”: Cane non “e” un Animale, lo contiene.
  • Nessuna gerarchia di tipi: non esiste casting tra tipo base e derivato.
  • Composizione esplicita: l’embedding e composizione, non ereditarieta.

Pattern pratici

Pattern Wrapper

type Contatore struct {
    sync.Mutex
    valore int
}

func (c *Contatore) Incrementa() {
    c.Lock()
    defer c.Unlock()
    c.valore++
}

func (c *Contatore) Valore() int {
    c.Lock()
    defer c.Unlock()
    return c.valore
}

Pattern Decorator

type RichiestaBase struct {
    URL    string
    Metodo string
}

type RichiestaAutenticata struct {
    RichiestaBase
    Token string
}

type RichiestaConLog struct {
    RichiestaAutenticata
    LogAttivo bool
}

Conclusione

L’embedding e uno dei meccanismi piu eleganti di Go. Permette di comporre tipi complessi a partire da tipi piu semplici, promuovendo automaticamente campi e metodi. A differenza dell’ereditarieta, l’embedding favorisce la composizione e la flessibilita, rendendo il codice piu modulare e facile da mantenere. Ricordate che l’embedding non crea relazioni gerarchiche tra tipi: per il polimorfismo, Go usa le interfacce.