embedding (încorporare) în Go

Metode, interfețe și generics Go 1.0 (câmpuri promovate în literale: Go 1.27)

Include un tip în altul fără nume de câmp; câmpurile și metodele lui devin accesibile direct (compoziție).

Sintaxă
type Persoana struct { Nume string }

type Angajat struct {
    Persoana          // câmp încorporat, fără nume
    Salariu float64
}

a.Nume   // câmpul lui Persoana, „promovat”

Ce face embedding (încorporare)

Go nu are moștenire. În loc de ea, un struct poate încorpora alt tip scriind doar numele tipului, fără nume de câmp. Câmpurile și metodele tipului încorporat sunt „promovate”: a.Nume funcționează ca prescurtare pentru a.Persoana.Nume. Tipul exterior implementează astfel automat interfețele pe care le implementează cel încorporat.

Din Go 1.27, câmpurile promovate se pot da direct și în literal: Angajat{Nume: "Ana", Functie: "programator"}; înainte trebuia scris Angajat{Persoana: Persoana{Nume: "Ana"}, ...}. Dacă tipul exterior are un câmp sau o metodă cu același nume, aceasta o ascunde pe cea încorporată (dar ea rămâne accesibilă prin numele complet). Valoarea încorporată nu „știe” de tipul exterior: metodele ei lucrează doar pe partea ei.

Interfețele se pot și ele încorpora: type ReadWriter interface { Reader; Writer }. Un tip des încorporat este sync.Mutex, care adaugă metodele Lock și Unlock.

Exemple

Exemplul 1: Câmpuri și metode promovate

package main

import "fmt"

type Persoana struct {
    Nume string
    Oras string
}

func (p Persoana) Prezentare() string {
    return p.Nume + " din " + p.Oras
}

type Angajat struct {
    Persoana
    Functie string
}

func main() {
    a := Angajat{Persoana: Persoana{"Ana", "Iași"}, Functie: "programator"}
    fmt.Println(a.Nume)          // promovat
    fmt.Println(a.Persoana.Oras) // nume complet
    fmt.Println(a.Prezentare(), "-", a.Functie)

    // Go 1.27: câmpurile promovate direct în literal
    b := Angajat{Nume: "Radu", Oras: "Cluj", Functie: "tester"}
    fmt.Printf("%+v\n", b)
}

Rezultat

Ana
Iași
Ana din Iași - programator
{Persoana:{Nume:Radu Oras:Cluj} Functie:tester}

Exemplul 2: Suprascriere și sync.Mutex încorporat

Angajat.Prezentare o ascunde pe cea a lui Persoana, dar o poate apela.

package main

import (
    "fmt"
    "sync"
)

type Persoana struct{ Nume string }

func (p Persoana) Prezentare() string { return "Sunt " + p.Nume }

type Angajat struct {
    Persoana
    Firma string
}

func (a Angajat) Prezentare() string {
    return a.Persoana.Prezentare() + " și lucrez la " + a.Firma
}

type Contor struct {
    sync.Mutex
    vizite int
}

func main() {
    a := Angajat{Persoana{"Mihai"}, "Dexblog"}
    fmt.Println(a.Prezentare())

    var c Contor
    c.Lock()
    c.vizite++
    c.Unlock()
    fmt.Println("vizite:", c.vizite)
}

Rezultat

Sunt Mihai și lucrez la Dexblog
vizite: 1

Greșeli frecvente

  • Așteptarea unui comportament de moștenire: o metodă a lui Persoana care apelează p.Prezentare() folosește tot versiunea lui Persoana, nu pe cea a lui Angajat.
  • Același nume promovat din două tipuri încorporate: ambiguous selector a.Nume. Folosește numele complet.
  • Încorporarea unui sync.Mutex într-un tip exportat face Lock/Unlock publice; pentru tipuri publice preferă un câmp cu nume (mu sync.Mutex).

Comenzi înrudite

  • struct și struct-uri: Grupează mai multe câmpuri cu nume și tipuri diferite într-un singur tip; baza pentru modelarea datelor în Go.
  • metode: Funcții legate de un tip prin receptor; cu receptor pointer pot modifica valoarea pe care sunt apelate.
  • interface și interfețe: Interfața descrie un set de metode; orice tip care le are o implementează automat, fără declarație explicită.