Menu
Coddy logo textTech

Incorporação vs Agregação

Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 38 de 107.

Você aprendeu que a incorporação (embedding) promove campos e métodos para a struct externa. Mas o Go também suporta agregação, onde você inclui outra struct como um campo nomeado. Entender quando usar cada abordagem é fundamental para um bom design.

Com embedding, você omite o nome do campo, e os membros do tipo interno tornam-se diretamente acessíveis:

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // embedding - sem nome de campo
}

d := Document{}
d.Write()  // acesso direto

Com agregação, você dá ao campo um nome explícito, exigindo esse nome para acessar o tipo interno:

type Document struct {
    writer Writer  // agregação - campo nomeado
}

d := Document{writer: Writer{}}
d.writer.Write()  // deve usar o nome do campo

A principal diferença é o relacionamento que você está expressando. A incorporação (Embedding) sugere um relacionamento "is-a" (é-um), onde o tipo externo assume a identidade do tipo interno. A agregação (Aggregation) sugere um relacionamento "has-a" (tem-um), onde o tipo externo meramente contém o tipo interno.

Use embedding quando desejar que o tipo externo satisfaça interfaces implementadas pelo tipo interno, ou quando o acesso direto a métodos tornar a API mais limpa. Use agregação quando o tipo interno for um detalhe de implementação que não deve ser exposto, ou quando desejar controle explícito sobre como o tipo interno é acessado.

challenge icon

Desafio

Fácil

Vamos construir um sistema de geração de relatórios que demonstra quando usar incorporação (embedding) versus agregação. Você criará um sistema onde alguns componentes devem ser acessíveis diretamente (incorporação), enquanto outros devem permanecer como detalhes de implementação interna (agregação).

Você organizará seu código em três arquivos:

  • formatter.go: Crie uma struct Formatter com um campo Style (string) e um método Format(text string) string que retorna o texto envolvido pelas tags de estilo, como [Style] text [/Style]. Este será um auxiliar interno que não deve ser exposto diretamente.
  • report.go: Crie duas structs que lidam com a geração de relatórios de forma diferente:
    • Uma struct Metadata com os campos Author e Date—isso representa informações públicas que devem ser acessíveis diretamente
    • Uma struct Report com um campo Title que incorpora (embeds) Metadata (para acesso direto ao autor e à data), mas usa agregação para Formatter (como um detalhe de implementação interna com um campo nomeado formatter)
    Adicione um método Generate() string à Report que usa o formatador interno para formatar o título e inclui os metadados.
  • main.go: Leia os detalhes do relatório da entrada, crie um Report com metadados incorporados e um formatador agregado. Demonstre a diferença acessando o autor diretamente no relatório (através da incorporação) e, em seguida, gerando o relatório formatado (que usa o formatador oculto internamente).

As seguintes entradas serão fornecidas:

  • Linha 1: Título do relatório
  • Linha 2: Nome do autor
  • Linha 3: Data
  • Linha 4: Estilo do formatador

Seu método Format deve retornar:

[Style] text [/Style]

Seu método Generate deve retornar:

[formatted title]
Author: [Author], Date: [Date]

No seu arquivo principal, imprima três linhas:

  1. O autor acessado diretamente no Report (demonstrando que a incorporação promove o campo)
  2. A data acessada através do nome do tipo incorporado (report.Metadata.Date)
  3. O resultado da chamada de Generate()

Por exemplo, dados Quarterly Results, Jane Doe, 2024-01-15 e BOLD, sua saída deve ser:

Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15

Observe como Author e Date são acessíveis diretamente no Report através da incorporação, enquanto o formatador permanece oculto—você não pode chamar report.Format() diretamente porque ele está agregado com um campo nomeado. Isso demonstra o relacionamento "é um" (is-a) versus "tem um" (has-a) na prática.

Folha de consulta

Go suporta duas maneiras de incluir uma struct dentro de outra: embedding (incorporação) e aggregation (agregação).

Embedding omite o nome do campo, promovendo os campos e métodos do tipo interno para a struct externa:

type Writer struct{}
func (w Writer) Write() string { return "writing" }

type Document struct {
    Writer  // embedding - sem nome de campo
}

d := Document{}
d.Write()  // acesso direto ao método incorporado

Aggregation usa um nome de campo explícito, exigindo esse nome para acessar o tipo interno:

type Document struct {
    writer Writer  // aggregation - campo nomeado
}

d := Document{writer: Writer{}}
d.writer.Write()  // deve usar o nome do campo

Quando usar cada um:

  • Embedding: Expressa um relacionamento "é-um" (is-a). Use quando o tipo externo deve satisfazer interfaces do tipo interno, ou quando o acesso direto ao método melhora a API.
  • Aggregation: Expressa um relacionamento "tem-um" (has-a). Use quando o tipo interno é um detalhe de implementação que não deve ser exposto, ou quando você deseja controle explícito sobre o acesso.

Com embedding, você ainda pode acessar os campos através do nome do tipo incorporado, se necessário:

type Metadata struct {
    Author string
}

type Report struct {
    Metadata  // incorporado
}

r := Report{Metadata: Metadata{Author: "Jane"}}
r.Author              // acesso direto via promoção
r.Metadata.Author     // acesso explícito via nome do tipo

Experimente você mesmo

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: Criar uma struct Metadata com o autor e a data
	
	// TODO: Criar um Formatter com o estilo fornecido
	
	// TODO: Criar um Report que incorpora Metadata e agrega Formatter
	
	// TODO: Imprimir o autor acessado diretamente no Report (a incorporação promove o campo)
	
	// TODO: Imprimir a data acessada através do nome do tipo incorporado (report.Metadata.Date)
	
	// TODO: Imprimir o resultado da chamada de Generate()
	
	// Suprimir avisos de variáveis não utilizadas (remova-os quando usar as variáveis)
	_ = title
	_ = author
	_ = date
	_ = style
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos