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 promove campos e métodos para a struct externa. Mas Go também oferece suporte à agregação, na qual você inclui outra struct como um campo nomeado. Entender quando usar cada abordagem é fundamental para um bom design.
Com o embedding, você omite o nome do campo, e os membros do tipo interno se tornam 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 a 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. Embedding sugere um relacionamento “is-a”, em que o tipo externo assume a identidade do tipo interno. Aggregation sugere um relacionamento “has-a”, em que o tipo externo apenas contém o tipo interno.
Use incorporação quando quiser que o tipo externo satisfaça as interfaces implementadas pelo tipo interno ou quando o acesso direto aos 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 quiser controle explícito sobre como o tipo interno é acessado.
Desafio
FácilVamos criar um sistema de geração de relatórios que demonstra quando usar incorporação em vez de agregação. Você criará um sistema no qual alguns componentes devem ser acessíveis diretamente (incorporação), enquanto outros devem permanecer como detalhes internos de implementação (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 pelo 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 maneiras diferentes:- Uma struct
Metadatacom os camposAuthoreDate. Ela representa informações públicas que devem ser acessíveis diretamente - Uma struct
Reportcom um campoTitleque incorporaMetadata(para acesso direto ao autor e à data), mas usa agregação paraFormatter(como um detalhe interno de implementação com um campo nomeadoformatter)
Generate() stringaReportque use o formatador interno para formatar o título e inclua os metadados.- Uma struct
main.go: Leia os detalhes do relatório a partir da entrada, crie umReportcom metadados incorporados e um formatador agregado. Demonstre a diferença acessando diretamente o autor no relatório (por meio da incorporação) e, em seguida, gerando o relatório formatado (que usa internamente o formatador oculto).
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 arquivo principal, imprima três linhas:
- O autor acessado diretamente no Report (demonstrando que a incorporação promove o campo)
- A data acessada por meio do nome do tipo incorporado (
report.Metadata.Date) - O resultado da chamada de
Generate()
Por exemplo, considerando 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 por meio da incorporação, enquanto o formatador permanece oculto. Você não pode chamar report.Format() diretamente porque ele foi agregado com um campo nomeado. Isso demonstra, na prática, a relação “é um” versus “tem um”.
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: Crie uma struct Metadata com o author e a date
// TODO: Crie um Formatter com o style fornecido
// TODO: Crie um Report que embute Metadata e agrega Formatter
// TODO: Imprima o author acessado diretamente no Report (embedding promove o campo)
// TODO: Imprima a date acessada através do nome do tipo embutido (report.Metadata.Date)
// TODO: Imprima o resultado de chamar Generate()
// Suprima avisos de variáveis não utilizadas (remova estes quando você 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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online