Menu
Coddy logo textTech

Padrão Composite

Parte da seção Programação Orientada a Objetos do Journey de Java da Coddy — lição 75 de 87.

O Composite Pattern é um padrão de projeto estrutural que permite compor objetos em estruturas de árvore para representar hierarquias parte-todo. Ele permite que os clientes tratem objetos individuais e composições de objetos de maneira uniforme por meio de uma interface comum.

Pense em um sistema de arquivos: uma pasta pode conter arquivos e outras pastas, que podem conter mais arquivos e pastas. Quer você esteja lidando com um único arquivo ou com uma estrutura de pastas inteira, você deseja realizar operações como "get size" da mesma maneira. O padrão define uma interface Component, objetos Leaf (elementos individuais) e objetos Composite (contêineres que contêm filhos):

interface FileComponent {
    void display(String indent);
    int getSize();
}

class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

O composite delega operações aos seus filhos, permitindo estruturas recursivas. Os clientes interagem com a árvore sem saber se estão trabalhando com uma folha ou um composite:

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

O Padrão Composite é ideal para representar hierarquias como organogramas, componentes de UI ou sistemas de menu, onde você precisa tratar grupos e indivíduos de forma idêntica.

challenge icon

Desafio

Fácil

Vamos construir um sistema de organograma usando o Composite Pattern! Você criará uma estrutura onde departamentos podem conter funcionários e outros subdepartamentos, permitindo calcular salários totais e exibir a hierarquia de forma uniforme — quer você esteja olhando para um único funcionário ou para uma divisão inteira.

Você organizará seu código em quatro arquivos:

  • OrganizationComponent.java: Defina a interface do componente que tanto os funcionários quanto os departamentos implementarão. Ela deve declarar dois métodos: showDetails(String indent) para exibir o componente com a indentação adequada, e getSalary() que retorna o salário total como um número inteiro.
  • Employee.java: Crie a classe folha (leaf) que representa trabalhadores individuais. Um Employee possui um nome (String) e salário (int), ambos definidos através do construtor. Quando showDetails é chamado, ele deve imprimir [indent][name]: $[salary]. O método getSalary simplesmente retorna o salário do funcionário.
  • Department.java: Crie a classe composta (composite) que pode conter tanto funcionários quanto subdepartamentos. Um Department possui um nome (String) e mantém uma lista de filhos do tipo OrganizationComponent. Ele deve ter um método add(OrganizationComponent component) para adicionar membros. Quando showDetails é chamado, ele imprime [indent][name] Department, e então chama showDetails em cada filho com indentação aumentada (adicione dois espaços). O método getSalary retorna a soma dos salários de todos os filhos.
  • Main.java: Construa sua organização! Você receberá quatro entradas: dois nomes de funcionários com seus salários, e um nome de subdepartamento com um funcionário.

    Crie um departamento "Engineering" como sua raiz. Adicione dois funcionários a ele usando os dois primeiros pares de nome/salário. Em seguida, crie um subdepartamento usando a terceira entrada, adicione um funcionário a ele usando o quarto par de nome/salário e adicione este subdepartamento ao Engineering.

    Chame showDetails("") no departamento Engineering e, em seguida, imprima o salário total no formato Total Salary: $[amount].

Você receberá as entradas nesta ordem: nome do funcionário1 (String), salário do funcionário1 (int), nome do funcionário2 (String), salário do funcionário2 (int), nome do subdepartamento (String), nome do funcionário3 (String), salário do funcionário3 (int).

Por exemplo, com as entradas Alice, 75000, Bob, 65000, QA, Charlie, 55000, sua saída seria:

Engineering Department
  Alice: $75000
  Bob: $65000
  QA Department
    Charlie: $55000
Total Salary: $195000

Observe como os mesmos métodos showDetails e getSalary funcionam perfeitamente, sejam chamados para um único funcionário ou para um departamento inteiro contendo subdepartamentos aninhados. A estrutura composta lida com a recursão automaticamente, permitindo que você trate toda a hierarquia de forma uniforme!

Folha de consulta

O Composite Pattern (Padrão de Composição) é um padrão de design estrutural que compõe objetos em estruturas de árvore para representar hierarquias parte-todo. Ele permite tratar objetos individuais e composições de forma uniforme através de uma interface comum.

O padrão consiste em três elementos-chave:

  • Component: Interface que define operações comuns
  • Leaf: Objetos individuais que implementam a interface do componente
  • Composite: Objetos de contêiner que contêm filhos e delegam operações a eles

Exemplo de implementação de um sistema de arquivos:

// Component interface
interface FileComponent {
    void display(String indent);
    int getSize();
}

// Leaf class
class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

// Composite class
class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

Usando a estrutura composta:

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

O composite delega operações aos seus filhos recursivamente. Os clientes interagem com a árvore sem saber se estão trabalhando com uma folha (leaf) ou uma composição (composite).

Casos de uso: Organogramas, componentes de interface do usuário (UI), sistemas de menu, sistemas de arquivos — qualquer hierarquia onde grupos e indivíduos precisem de tratamento idêntico.

Experimente você mesmo

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Ler as entradas
        String emp1Name = scanner.nextLine();
        int emp1Salary = Integer.parseInt(scanner.nextLine());
        String emp2Name = scanner.nextLine();
        int emp2Salary = Integer.parseInt(scanner.nextLine());
        String subDeptName = scanner.nextLine();
        String emp3Name = scanner.nextLine();
        int emp3Salary = Integer.parseInt(scanner.nextLine());
        
        // TODO: Criar o departamento "Engineering" como a raiz
        
        // TODO: Criar e adicionar dois funcionários a Engineering usando os dados de emp1 e emp2
        
        // TODO: Criar um subdepartamento usando subDeptName
        
        // TODO: Criar e adicionar um funcionário ao subdepartamento usando os dados de emp3
        
        // TODO: Adicionar o subdepartamento a Engineering
        
        // TODO: Chamar showDetails("") no departamento Engineering
        
        // TODO: Imprimir o salário total no formato: Total Salary: $[amount]
    }
}
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