Padrão Composite
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 96 de 107.
O padrão Composite permite que você trate objetos individuais e grupos de objetos de forma uniforme. Enquanto o State altera o comportamento com base no estado interno, o Composite constrói estruturas em árvore onde tanto as folhas quanto os containers compartilham a mesma interface.
Este padrão é perfeito para estruturas hierárquicas como sistemas de arquivos, organogramas ou componentes de UI. Em Go, definimos uma interface comum que tanto os itens individuais quanto os containers implementam:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}O composite (contêiner) contém filhos e implementa a mesma interface ao agregar seus resultados:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Agora você pode aninhar pastas dentro de pastas, e chamar GetSize() funciona de forma idêntica, seja para um único arquivo ou para uma árvore de diretórios inteira:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175O Composite é ideal quando você precisa representar hierarquias parte-todo e deseja que os clientes tratem objetos individuais e composições de forma idêntica.
Desafio
FácilVamos construir um sistema de organograma usando o padrão Composite! Você criará uma estrutura onde tanto funcionários individuais quanto departamentos (que contêm funcionários ou outros departamentos) podem ser tratados de forma uniforme — perfeito para calcular salários totais em qualquer parte da organização.
Você organizará seu código em três arquivos:
component.go: Defina a interface comum que tanto indivíduos quanto grupos implementarão.Crie uma interface
OrgComponentcom dois métodos:GetSalary() int— retorna o salário total para este componenteGetName() string— retorna o nome do funcionário ou departamento
organization.go: Implemente os tipos leaf (funcionário individual) e composite (departamento).Crie uma struct
Employeecom os camposName(string) eSalary(int). Seu métodoGetSalary()retorna seu próprio salário, eGetName()retorna seu nome.Crie uma struct
Departmentcom um campoName(string) e um sliceMembersque contém itensOrgComponent. Adicione um métodoAdd(c OrgComponent)para adicionar membros. SeuGetSalary()deve retornar a soma dos salários de todos os membros, eGetName()retorna o nome do departamento.main.go: Construa uma estrutura organizacional e calcule os salários.Leia o número de funcionários. Para cada funcionário, leia seu nome e salário, criando instâncias de
Employee. Em seguida, leia o número de departamentos. Para cada departamento, leia seu nome e a contagem de índices de membros, seguidos por esses índices (baseados em 0, referindo-se a funcionários ou departamentos criados anteriormente na ordem de criação).Após construir a estrutura, leia um índice final e imprima o nome desse componente e o salário total no formato:
[name]: [salary]
As seguintes entradas serão fornecidas:
- Número de funcionários, depois o nome e o salário de cada funcionário em linhas separadas
- Número de departamentos, depois o nome de cada departamento, contagem de membros e índices de membros
- Índice final para consulta
Por exemplo, dado:
3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3Sua saída deve ser:
Engineering: 165000E dado:
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Sua saída deve ser:
QA: 125000E dado:
2
Alice
50000
Bob
60000
0
1Sua saída deve ser:
Bob: 60000Observe como o GetSalary() funciona de forma idêntica, quer você o chame para um único funcionário ou para um departamento inteiro — o padrão Composite permite tratar indivíduos e grupos uniformemente através da mesma interface!
Folha de consulta
O padrão Composite trata objetos individuais e grupos de objetos de forma uniforme por meio de uma interface compartilhada. É ideal para estruturas hierárquicas como sistemas de arquivos, organogramas ou componentes de UI.
Defina uma interface comum que tanto os itens individuais quanto os containers implementam:
type Component interface {
GetSize() int
}Implemente a folha (item individual):
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}Implemente o composite (container) que contém filhos e agrega seus resultados:
type Folder struct {
Name string
Children []Component
}
func (f *Folder) Add(c Component) {
f.Children = append(f.Children, c)
}
func (f Folder) GetSize() int {
total := 0
for _, child := range f.Children {
total += child.GetSize()
}
return total
}Exemplo de uso mostrando o tratamento uniforme de itens individuais e composições:
docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})
root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})
fmt.Println(root.GetSize()) // 175Experimente você mesmo
package main
import "fmt"
func main() {
// Ler o número de funcionários
var numEmployees int
fmt.Scanln(&numEmployees)
// Armazenar todos os componentes (funcionários e departamentos) na ordem de criação
var components []OrgComponent
// TODO: Ler o nome e o salário de cada funcionário
// Criar instâncias de Employee e adicioná-las ao slice components
for i := 0; i < numEmployees; i++ {
var name string
var salary int
fmt.Scanln(&name)
fmt.Scanln(&salary)
// TODO: Criar funcionário e adicionar a components
}
// Ler o número de departamentos
var numDepartments int
fmt.Scanln(&numDepartments)
// TODO: Ler o nome de cada departamento, a contagem de membros e os índices dos membros
// Criar instâncias de Department, adicionar membros por índice e adicionar a components
for i := 0; i < numDepartments; i++ {
var deptName string
var memberCount int
fmt.Scanln(&deptName)
fmt.Scanln(&memberCount)
// TODO: Criar departamento, ler índices de membros, adicionar membros, adicionar a components
}
// Ler o índice final para consulta
var queryIndex int
fmt.Scanln(&queryIndex)
// TODO: Imprimir o nome do componente e o salário total no formato: [name]: [salary]
// Exemplo: fmt.Printf("%s: %d\n", name, salary)
}
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 Parser11Biblioteca Padrão & POO
io.Reader & io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRecapitulação - Modelos de API REST14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeMiddleware como Decorator3Ponteiros 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