Menu
Coddy logo textTech

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())  // 175

O Composite é ideal quando você precisa representar hierarquias parte-todo e deseja que os clientes tratem objetos individuais e composições de forma idêntica.

challenge icon

Desafio

Fácil

Vamos 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 OrgComponent com dois métodos:

    • GetSalary() int — retorna o salário total para este componente
    • GetName() string — retorna o nome do funcionário ou departamento
  • organization.go: Implemente os tipos leaf (funcionário individual) e composite (departamento).

    Crie uma struct Employee com os campos Name (string) e Salary (int). Seu método GetSalary() retorna seu próprio salário, e GetName() retorna seu nome.

    Crie uma struct Department com um campo Name (string) e um slice Members que contém itens OrgComponent. Adicione um método Add(c OrgComponent) para adicionar membros. Seu GetSalary() deve retornar a soma dos salários de todos os membros, e GetName() 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
3

Sua saída deve ser:

Engineering: 165000

E dado:

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

Sua saída deve ser:

QA: 125000

E dado:

2
Alice
50000
Bob
60000
0
1

Sua saída deve ser:

Bob: 60000

Observe 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())  // 175

Experimente 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)
}
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