Uma pasta dentro de outra, com mais arquivos dentro. Se você já lidou com esse tipo de estrutura em árvore no código, provavelmente se sentiu perdido pelo menos uma vez.
Se você usa if toda vez para separar «isto é uma pasta ou um arquivo?», o código fica confuso rapidamente.
É um problema comum ao criar algo parecido com um explorador de arquivos. Nessa situação, a ferramenta certa é o padrão Composite.
O padrão Composite trata objetos individuais (arquivos) e seus agrupamentos (pastas) como o mesmo tipo, permitindo lidar com toda a árvore como um único objeto.
Hoje vou mostrar como implementar esse padrão em Swift, usando o código que escrevi na prática.
Resumo dos pontos principais
- Padrão Composite = agrupar folhas e composites no mesmo protocolo
- O cliente faz a mesma chamada sem distinguir arquivos de pastas
- O cálculo da pasta pergunta novamente aos filhos, usando uma estrutura recursiva
- No Swift, é possível implementar isso de forma simples com apenas
protocol
O que é o padrão Composite?
Literalmente, é «composição»: reunir coisas pequenas para formar uma unidade maior.
A analogia mais comum é um sistema de arquivos.
Um arquivo é uma folha que termina em si mesma. Já uma pasta é um ramo que pode conter arquivos ou outras pastas.
Mas a perspectiva do usuário é diferente. Ao abrir uma pasta ou um arquivo, simplesmente damos o comando «Abrir».
O objetivo do padrão Composite é tratar elementos individuais e agrupamentos da mesma maneira.
Os componentes se dividem em três tipos.
- Component: interface comum (protocolo)
- Leaf: objeto terminal sem filhos (arquivo)
- Composite: objeto que contém filhos (pasta)
Como implementar em Swift?
Primeiro, defina o protocolo comum para consultar o nome e o tamanho.
A seguir estão as regras (Component) que arquivos e pastas seguem em conjunto.
protocol FileComponent {
var name: String { get }
func size() -> Int // retorna o tamanho em bytes
}
Agora temos o arquivo, que corresponde à folha. Basta retornar seu próprio tamanho, então é simples.
struct File: FileComponent {
let name: String
let bytes: Int
func size() -> Int { bytes } // retorna o próprio tamanho sem alterações
}
O ponto principal é a pasta (Composite). Ela armazena os filhos em um array e, quando consultada sobre o tamanho, pergunta novamente aos filhos.
struct Folder: FileComponent {
let name: String
var children: [FileComponent] = []
func size() -> Int {
children.reduce(0) { $0 + $1.size() } // soma dos filhos (recursiva)
}
}
O ponto importante é que size() chama a si mesma novamente, formando uma estrutura recursiva.
Não importa quantos níveis de pastas existam: essa linha percorre tudo automaticamente até o fim.
Na prática, fica assim
Agora vamos criar uma árvore misturando arquivos e pastas.
let root = Folder(name: "Documentos", children: [
File(name: "Notas.txt", bytes: 100),
Folder(name: "Fotos", children: [
File(name: "Viagem.jpg", bytes: 2000)
])
])
print(root.size()) // 2100 imprimir
Viu? Uma única chamada a root.size() soma o tamanho de todos os arquivos da pasta.
Essa foi a parte que mais me impressionou quando executei o código.
No código do cliente, nunca perguntamos «isto é uma pasta ou um arquivo?».
Basta chamar size(). Cada objeto cuida do restante.
Qual é a diferença em relação a usar condicionais?
Você deve estar pensando: «Não posso simplesmente separar os tipos com uma instrução if e processá-los?»
Pode. Mas, quanto mais profunda a árvore, maior fica a diferença.
| Categoria | Abordagem com condicionais (if/switch) | Padrão Composite |
|---|---|---|
| Identificação do tipo | Verificar manualmente todas as vezes | Não é necessário |
| Adicionar um novo tipo | Modificar condicionais em vários lugares | Adotar o protocolo |
| Processamento recursivo | Escrever o código de percurso manualmente | O objeto cuida disso |
| Legibilidade do código | Fica mais complexo com a profundidade | Mantém-se consistente |
Na abordagem condicional, ao surgir um novo tipo (como um link de atalho), é preciso adicionar if novamente em vários lugares.
Com Composite, basta criar um novo tipo que adote FileComponent. Não é necessário alterar o código existente.
Perguntas frequentes
P. As interfaces de Leaf e Composite precisam ser exatamente iguais?
Idealmente, sim. Mas uma função como «adicionar filho» não faz sentido para um arquivo. Nesse caso, coloque no protocolo apenas os comportamentos comuns (como size) e deixe o gerenciamento dos filhos exclusivamente para as pastas.
P. Posso usar class em vez de struct?
Sim, como no exemplo acima, um tipo por valor também funciona bem. Porém, se você altera a árvore com frequência e precisa compartilhar referências, class pode ser mais conveniente.
Se estruturas em árvore colocaram você no inferno de if, experimente usar o padrão Composite.
Ele funciona bem não apenas em sistemas de arquivos, mas também em hierarquias de views, menus e organogramas — qualquer estrutura com «mais coisas dentro».
Cole o exemplo de hoje no Playground e execute-o; a ideia ficará clara rapidamente.

