Structs aninhadas e campos anônimos

1. Introdução às Structs em Go

Structs em Go são tipos de dados compostos que agregam campos nomeados de diferentes tipos. Elas são fundamentais para modelar dados estruturados, permitindo criar tipos personalizados que representam entidades do mundo real.

type Pessoa struct {
    Nome  string
    Idade int
    Email string
}

// Inicialização básica
p1 := Pessoa{"João", 30, "joao@email.com"}
p2 := Pessoa{Nome: "Maria", Idade: 25, Email: "maria@email.com"}

// Acesso a campos
fmt.Println(p1.Nome) // João

2. Structs Aninhadas: Composição de Dados

Structs aninhadas permitem compor estruturas complexas colocando uma struct como campo de outra. Isso cria uma hierarquia natural de dados.

type Endereco struct {
    Rua    string
    Numero int
    Cidade string
    CEP    string
}

type Cliente struct {
    Nome     string
    Endereco Endereco // Struct aninhada
}

// Acesso a campos aninhados
cliente := Cliente{
    Nome: "Carlos",
    Endereco: Endereco{
        Rua:    "Av. Paulista",
        Numero: 1000,
        Cidade: "São Paulo",
        CEP:    "01310-100",
    },
}

fmt.Println(cliente.Endereco.Cidade) // São Paulo

3. Structs Aninhadas com Ponteiros e Slice

Ponteiros para structs evitam cópias desnecessárias, especialmente em estruturas grandes. Slices de structs são úteis para modelar coleções.

type Item struct {
    Produto  string
    Quantidade int
    Preco    float64
}

type Pedido struct {
    ID     int
    Itens  []Item          // Slice de structs
    Cliente *Cliente       // Ponteiro para struct
}

// Exemplo prático: sistema de pedidos
pedido := Pedido{
    ID: 1,
    Itens: []Item{
        {Produto: "Notebook", Quantidade: 1, Preco: 4500.00},
        {Produto: "Mouse", Quantidade: 2, Preco: 150.00},
    },
    Cliente: &Cliente{
        Nome: "Ana",
        Endereco: Endereco{Rua: "Rua das Flores", Numero: 50, Cidade: "Rio de Janeiro", CEP: "20040-020"},
    },
}

// Calculando total do pedido
total := 0.0
for _, item := range pedido.Itens {
    total += float64(item.Quantidade) * item.Preco
}
fmt.Printf("Total do pedido %d: R$ %.2f\n", pedido.ID, total)
fmt.Printf("Cliente: %s, Cidade: %s\n", pedido.Cliente.Nome, pedido.Cliente.Endereco.Cidade)

4. Campos Anônimos (Embedding) – Herança sem Herança

Campos anônimos permitem incorporar um tipo em outro sem nomear explicitamente o campo. Isso promove a composição como alternativa à herança tradicional.

type Animal struct {
    Nome  string
    Som   string
}

func (a Animal) EmitirSom() string {
    return a.Som
}

// Campo anônimo: tipo Cachorro "herda" de Animal
type Cachorro struct {
    Animal        // Campo anônimo
    Raca string
}

// Promoção de campos e métodos
dog := Cachorro{
    Animal: Animal{Nome: "Rex", Som: "Au Au"},
    Raca:   "Labrador",
}

// Acesso direto a campos promovidos
fmt.Println(dog.Nome)       // Rex (promovido de Animal)
fmt.Println(dog.EmitirSom()) // Au Au (método promovido)
fmt.Println(dog.Raca)       // Labrador

Diferença entre aninhamento e embedding:
- Aninhamento: campo nomeado (Endereco Endereco) → acesso via cliente.Endereco.Cidade
- Embedding: campo anônimo (Animal) → acesso direto (dog.Nome)

5. Conflitos e Sobrescrita com Campos Anônimos

Quando tipos embutidos e o tipo externo têm campos com o mesmo nome, o campo do nível mais externo tem prioridade.

type Base struct {
    Nome string
}

type Derivada struct {
    Base
    Nome string // Sobrescreve Base.Nome
}

d := Derivada{
    Base: Base{Nome: "Base"},
    Nome: "Derivada",
}

fmt.Println(d.Nome)      // Derivada (prioridade do nível externo)
fmt.Println(d.Base.Nome) // Base (acesso explícito ao campo embutido)

6. Métodos com Structs Aninhadas e Campos Anônimos

Métodos definidos no tipo embutido são automaticamente promovidos para o tipo externo, permitindo sobrescrita de comportamento.

type Veiculo struct {
    Marca  string
    Modelo string
}

func (v Veiculo) Acelerar() string {
    return "Veículo acelerando..."
}

type Carro struct {
    Veiculo
    Portas int
}

// Sobrescrita de método
func (c Carro) Acelerar() string {
    return "Carro acelerando rapidamente!"
}

c := Carro{
    Veiculo: Veiculo{Marca: "Toyota", Modelo: "Corolla"},
    Portas:  4,
}

fmt.Println(c.Acelerar())        // Carro acelerando rapidamente! (sobrescrito)
fmt.Println(c.Veiculo.Acelerar()) // Veículo acelerando... (acesso explícito)

7. Boas Práticas e Casos de Uso Reais

Exemplo prático: sistema de usuário com permissões

type Permissoes struct {
    Admin     bool
    Editor    bool
    Leitor    bool
}

type Usuario struct {
    Nome      string
    Email     string
    Permissoes // Embedding para promover campos
}

func (u Usuario) PodeEditar() bool {
    return u.Admin || u.Editor
}

usuario := Usuario{
    Nome:  "João",
    Email: "joao@sistema.com",
    Permissoes: Permissoes{Admin: false, Editor: true, Leitor: true},
}

fmt.Printf("%s pode editar? %v\n", usuario.Nome, usuario.PodeEditar()) // true

Boas práticas:
- Prefira composição sobre herança: structs aninhadas e embedding são mais flexíveis
- Evite aninhamento profundo (mais de 3 níveis) para manter a legibilidade
- Use embedding para "herdar" comportamento, não apenas dados
- Documente campos anônimos explicitamente para evitar confusão

8. Considerações Finais e Comparação com Outras Linguagens

Go adota composição como alternativa à herança de classes tradicional. Diferente de linguagens como Java ou C++, Go não possui herança, mas oferece embedding como mecanismo de reutilização.

Comparação rápida:
- Go (embedding): type Carro struct { Veiculo; Portas int } → promove campos e métodos
- C (structs aninhadas): struct Carro { struct Veiculo v; int portas; } → acesso via carro.v.marca
- Java (herança): class Carro extends Veiculo { int portas; } → herança com hierarquia rígida

Dicas para serialização JSON:

type Produto struct {
    Nome  string  `json:"nome"`
    Preco float64 `json:"preco"`
}

type Loja struct {
    Nome     string    `json:"nome"`
    Produtos []Produto `json:"produtos"`
}

loja := Loja{Nome: "TechStore", Produtos: []Produto{{Nome: "Notebook", Preco: 4500.00}}}
jsonBytes, _ := json.Marshal(loja)
fmt.Println(string(jsonBytes))
// {"nome":"TechStore","produtos":[{"nome":"Notebook","preco":4500}]}

Structs aninhadas e campos anônimos em Go oferecem um modelo de composição poderoso e flexível, permitindo construir sistemas complexos sem as armadilhas da herança tradicional. A chave está em entender quando usar cada abordagem: structs aninhadas para hierarquias de dados explícitas e campos anônimos para reutilização de comportamento.

Referências