Pular para o conteúdo
Estruturais

Composite

Compõe objetos em estruturas de árvore para representar hierarquias parte-todo, permitindo que clientes tratem objetos individuais e composições de forma uniforme.

Intenção

Permitir que clientes trabalhem com estruturas de árvore complexas tratando tanto nós folha quanto ramificações através da mesma interface, para que adicionar, remover, ou operar sobre elementos não exija que o cliente distinga entre objetos simples e compostos.

Problema

Seu domínio tem uma hierarquia natural parte-todo -- arquivos e diretórios, departamentos e funcionários, ou containers de UI e widgets. O código cliente que percorre ou opera sobre essa estrutura fica cheio de checagens de tipo e lógica condicional para distinguir entre nós folha e containers compostos, tornando-o frágil e difícil de estender com novos tipos de nó.

Solução

Defina uma interface Component que declara operações comuns tanto a objetos folha quanto compostos. Nós folha implementam as operações diretamente. Nós compostos armazenam componentes filhos e implementam operações delegando a cada filho. Como ambos seguem a mesma interface, os clientes percorrem e manipulam a árvore inteira de forma uniforme.

Participantes

  • Component -- declara a interface compartilhada por objetos folha e compostos
  • Leaf -- representa objetos finais que não têm filhos
  • Composite -- armazena Components filhos e implementa operações iterando sobre os filhos
  • Client -- manipula objetos através da interface Component

Vantagens

  • Clientes tratam objetos folha e compostos de forma uniforme, reduzindo lógica condicional
  • Fácil adicionar novos tipos de componente -- basta implementar a interface Component
  • Estruturas recursivas são modeladas e percorridas naturalmente
  • Simplifica o código cliente eliminando a necessidade de distinguir entre tipos de nó

Desvantagens

  • Tornar o design genérico demais pode dificultar restringir os tipos de filhos que um composite aceita
  • A segurança de tipos é mais fraca -- a interface compartilhada pode incluir operações sem sentido para folhas (ex: addChild)
  • Pode tornar o design excessivamente abstrato se a hierarquia for simples
  • Ordenar ou limitar filhos frequentemente exige controle extra

Analogia do mundo real

Um organograma é uma estrutura composite. Um departamento (composite) contém times, que contêm funcionários individuais (leaves). Quando o CEO pede o total de funcionários, a requisição se propaga pela árvore: cada departamento soma seus times, cada time soma seus membros, e os resultados sobem. O CEO não se importa se um nó é uma pessoa ou um departamento -- a operação de contagem funciona de forma uniforme.

Casos de uso

  • Sistema de arquivos: arquivos (leaves) e diretórios (composites) compartilhando uma interface comum FileSystemNode
  • Árvores de componentes de UI: containers que contêm botões, campos de texto, e outros containers
  • Organogramas: funcionários e departamentos com um método comum getHeadcount() ou getCost()
  • Sistemas de menu: itens de menu e submenus renderizados através da mesma interface
  • Árvores de expressão aritmética: números (leaves) e operações (composites) com um método comum evaluate()

Exemplos de código

FileSystemNode.java
import java.util.ArrayList;
import java.util.List;

interface FileSystemNode {
    String name();
    long getSize();
    void print(String indent);
}

final class FileNode implements FileSystemNode {
    private final String name;
    private final long size;

    FileNode(String name, long size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String name() { return name; }

    @Override
    public long getSize() { return size; }

    @Override
    public void print(String indent) {
        System.out.println(indent + name + " (" + size + " bytes)");
    }
}

final class DirectoryNode implements FileSystemNode {
    private final String name;
    private final List<FileSystemNode> children = new ArrayList<>();

    DirectoryNode(String name) {
        this.name = name;
    }

    void add(FileSystemNode node) {
        children.add(node);
    }

    @Override
    public String name() { return name; }

    @Override
    public long getSize() {
        return children.stream().mapToLong(FileSystemNode::getSize).sum();
    }

    @Override
    public void print(String indent) {
        System.out.println(indent + name + "/ (" + getSize() + " bytes)");
        for (FileSystemNode child : children) {
            child.print(indent + "  ");
        }
    }
}

public class CompositeDemo {
    public static void main(String[] args) {
        DirectoryNode root = new DirectoryNode("project");
        DirectoryNode src = new DirectoryNode("src");
        src.add(new FileNode("Main.java", 1200));
        src.add(new FileNode("Utils.java", 800));
        root.add(src);
        root.add(new FileNode("README.md", 400));

        root.print(""); // recursively prints the whole tree
        System.out.println("Total size: " + root.getSize()); // 2400
    }
}

Uma árvore de sistema de arquivos com uma interface comum FileSystemNode, onde FileNode é uma folha e DirectoryNode é um composite que calcula recursivamente o tamanho total.