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.
Desafio
FácilVamos 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 structFormattercom um campoStyle(string) e um métodoFormat(text string) stringque 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
Metadatacom os camposAuthoreDate—isso representa informações públicas que devem ser acessíveis diretamente - Uma struct
Reportcom um campoTitleque incorpora (embeds)Metadata(para acesso direto ao autor e à data), mas usa agregação paraFormatter(como um detalhe de implementação interna com um campo nomeadoformatter)
Generate() stringàReportque usa o formatador interno para formatar o título e inclui os metadados.- Uma struct
main.go: Leia os detalhes do relatório da entrada, crie umReportcom 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:
- O autor acessado diretamente no Report (demonstrando que a incorporação promove o campo)
- A data acessada através do nome do tipo incorporado (
report.Metadata.Date) - 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-15Observe 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
}
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
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool