Padrões de Projetos - Composite

De Aulas

Afluentes: Modelos, métodos e técnicas da engenharia de software

Descrição

O padrão Composite (ou Compósito) é usado para representar hierarquias de objetos que podem ser tratados de maneira uniforme. Ele permite que você organize objetos em estruturas de árvore, onde cada objeto pode ser um componente individual ou um grupo de componentes (compósitos). Com isso, você pode tratar tanto objetos individuais quanto grupos de forma consistente, pois ambos implementam a mesma interface.

Esse padrão é muito útil quando queremos manipular uma estrutura de objetos de forma recursiva, como em menus, diretórios de arquivos, ou representações gráficas com formas e grupos de formas.

Funcionamento do Composite

  1. Component: Interface ou classe abstrata que define a interface comum para todos os objetos na composição.
  2. Leaf (Folha): Representa um objeto simples que não contém outros objetos (um objeto “folha”).
  3. Composite: Representa um objeto que pode conter outros objetos (folhas ou compósitos), e implementa operações para adicionar, remover e manipular seus filhos.

Exemplo em Java

Vamos implementar um exemplo usando o padrão de projeto Composite para desenhar formas como círculos e retângulos, que podem ser organizadas em grupos e tratadas de forma uniforme usando o padrão Composite. Para isso, usaremos o Canvas do Java Swing e AWT. Usaremos o método paintComponent de um JPanel para desenhar no canvas, e os componentes gráficos serão desenhados diretamente no contexto gráfico Graphics.

Exemplo em Java com Java Swing

  1. Interface Forma: Representa a interface comum para todas as formas, com o método desenhar.
  2. Classes Circulo e Retangulo: Representam formas individuais.
  3. Classe ListaDeFiguras: Representa um grupo de formas que pode conter outras formas ou grupos.

Classe Figura

Interface comum para todas as formas, com o método desenhar(Graphics g).

import java.awt.Graphics;

// Interface comum para todas as formas
public interface Figura {
    void desenhar(Graphics g);
}

Classe Círculo

A classe Círculo Implementa Figura e representa objetos simples (folhas) que desenham um círculo no Graphics.

import java.awt.*;

// Classe para desenhar um círculo
class Circulo implements Figura {
    private int x, y, raio;
    private Color cor;

    public Circulo(int x, int y, int raio, Color cor) {
        this.x = x;
        this.y = y;
        this.raio = raio;
        this.cor = cor;
    }

    @Override
    public void desenhar(Graphics g) {
        g.setColor(cor);
        g.fillOval(x, y, raio * 2, raio * 2);
    }
}

Classe Retangulo

Tal qual a classe Círculo, mas desenha um retângulo.

import java.awt.*;

// Classe para desenhar um retângulo
class Retangulo implements Figura {
    private int x, y, largura, altura;
    private Color cor;

    public Retangulo(int x, int y, int largura, int altura, Color cor) {
        this.x = x;
        this.y = y;
        this.largura = largura;
        this.altura = altura;
        this.cor = cor;
    }

    @Override
    public void desenhar(Graphics g) {
        g.setColor(cor);
        g.fillRect(x, y, largura, altura);
    }
}

Classe ListaDeFiguras

Implementa Figura e contém uma lista de Figuras (folhas ou compostos). Ao chamar desenhar(Graphics g), ele delega a chamada para cada forma contida no grupo.

import java.awt.*;
import java.util.List;
import java.util.ArrayList;

// Composto no padrão Composite
class ListaDeFiguras implements Figura {
    private List<Figura> formas = new ArrayList<>();

    public void adicionar(Figura forma) {
        formas.add(forma);
    }

    public void remover(Figura forma) {
        formas.remove(forma);
    }

    @Override
    public void desenhar(Graphics g) {
        for (Figura forma : formas) {
            forma.desenhar(g);
        }
    }
}

Classe TelaDeDesenho

Herda JPanel e sobrepõe o método paintComponent para desenhar a estrutura de Figura (composta) no Graphics.

import java.awt.*;
import javax.swing.JPanel;

// JPanel personalizado que desenha as formas no Canvas
public class TelaDeDesenho extends JPanel {
    private Figura formaComposta;

    public TelaDeDesenho(Figura formaComposta) {
        this.formaComposta = formaComposta;
        setPreferredSize(new Dimension(400, 400));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (formaComposta != null) {
            formaComposta.desenhar(g);
        }
    }
}

Classe Main

Código principal do programa que configura a interface Swing, cria algumas formas, organiza-as em grupos e adiciona o painel ao JFrame para exibir a composição gráfica.

import javax.swing.*;
import java.awt.*;

public class Main {
    public static void main(String[] args) {
        // Criando algumas formas individuais
        Figura circulo1 = new Circulo(50, 50, 30, Color.BLUE);
        Figura retangulo1 = new Retangulo(150, 70, 80, 40, Color.RED);

        // Criando um grupo de formas (Composto)
        ListaDeFiguras grupo1 = new ListaDeFiguras();
        grupo1.adicionar(circulo1);
        grupo1.adicionar(retangulo1);

        // Criando outro grupo com novas formas e adicionando grupo1 dentro dele
        ListaDeFiguras grupo2 = new ListaDeFiguras();
        grupo2.adicionar(grupo1); // Grupo de formas dentro de outro grupo
        grupo2.adicionar(new Circulo(200, 200, 40, Color.GREEN));
        grupo2.adicionar(new Retangulo(250, 250, 100, 50, Color.PINK));

        // Configuração da janela Swing
        JFrame frame = new JFrame("Exemplo de Composite");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 400);

        // Adiciona o painel de desenho com o grupo de formas
        frame.add(new TelaDeDesenho(grupo2));

        // Exibe a janela
        frame.pack();
        frame.setVisible(true);
    }
}

Execução

Quando você executa o código, ele abre uma janela do Swing com as formas desenhadas no painel. A estrutura hierárquica permite que você trate todos os objetos como uma única entidade, o que torna o desenho muito flexível e extensível para estruturas mais complexas.

Vantagens do Composite

  1. Hierarquia Recursiva e Flexível:
    • Permite criar hierarquias recursivas de objetos de forma que componentes simples e compostos sejam tratados de maneira uniforme. Isso facilita o trabalho com estruturas complexas, como árvores, onde cada elemento pode ser outro grupo de elementos.
  2. Simplicidade para o Cliente:
    • O cliente que usa os objetos Composite não precisa se preocupar em diferenciar objetos simples de compostos. Ele interage com uma interface única e pode manipular todos os elementos como se fossem homogêneos.
  3. Facilidade de Expansão:
    • Como novos tipos de componentes podem ser adicionados sem modificar o código existente, o Composite promove a extensibilidade. Basta adicionar novos tipos de Leaf ou Composite, e eles poderão ser usados de forma transparente na estrutura hierárquica.
  4. Reduz Complexidade de Código:
    • Ao tratar elementos simples e compostos de forma uniforme, o padrão Composite ajuda a simplificar o código cliente, reduzindo condicionais e verificações para identificar o tipo de objeto.

Desvantagens do Composite

  1. Dificuldade de Implementação de Restrições:
    • Em algumas situações, pode ser necessário restringir certos componentes para serem compostos ou folhas. O Composite não diferencia explicitamente esses tipos, o que pode dificultar a implementação de restrições na hierarquia (como, por exemplo, evitar que uma folha contenha outras folhas).
  2. Complexidade de Manutenção:
    • Em estruturas hierárquicas muito grandes e complexas, o Composite pode levar a estruturas de objetos profundas e difíceis de gerenciar, o que pode impactar a performance e aumentar a dificuldade de depuração.
  3. Performance:
    • O Composite pode adicionar uma sobrecarga de processamento em hierarquias muito grandes, pois a execução de operações (como desenhar() ou adicionar()) em uma estrutura complexa pode envolver múltiplas chamadas recursivas ou iterações por vários objetos.
  4. Viola o Princípio de Responsabilidade Única:
    • Um Composite geralmente possui tanto a lógica para o comportamento de seus elementos quanto para a sua organização hierárquica (adicionar, remover, gerenciar filhos). Isso pode levar a um acúmulo de responsabilidades, o que vai contra o princípio de responsabilidade única.

Quando Usar o Composite

O padrão Composite é mais adequado quando:

  • Você tem uma estrutura hierárquica de objetos, como uma árvore.
  • Deseja tratar de forma uniforme objetos simples e composições de objetos.
  • Precisa adicionar elementos na estrutura sem modificar o código cliente.

Resumo

O Composite é poderoso para lidar com hierarquias de objetos, mas deve ser usado com atenção em projetos complexos para evitar problemas de performance e de organização.