Composite

Origem: Wikipédia, a enciclopédia livre.
 Nota: Se procura pelo material, veja Composite (material).

Entende-se por Composite um padrão de projeto de software utilizado para representar um objeto formado pela composição de objetos similares. Este conjunto de objetos pressupõe uma mesma hierarquia de classes a que ele pertence. Tal padrão é, normalmente, utilizado para representar listas recorrentes - ou recursivas - de elementos. Além disso, este modo de representação hierárquica de classes permite que os elementos contidos em um objeto composto sejam tratados como se fossem um objeto único. Desta forma, os métodos comuns às classes podem ser aplicados, também, ao conjunto agrupado no objeto composto.

Aplicação[editar | editar código-fonte]

A intenção do padrão Composite é compor objetos em estruturas de árvore para representar hierarquia partes-todo.

Por exemplo, em interfaces gráficas, um elemento gráfico pode ser formado pela composição de vários outros elementos. Uma página de internet pode conter um ou mais ícones, além de caixas de texto e vários outros elementos. Considerando que uma determinada hierarquia de classes indica um Elemento Gráfico como, portanto, a super-classe—comum à todas classes que representam elementos gráficos atômicos. Assim, a "página" pode ser representada tanto como uma classe que contém zero ou mais elementos gráficos. Veja diagrama a seguir.

Outro exemplo são as linguagens de programação. A hierarquia de classes é utilizada para representar os comandos da linguagem. Supondo que a super-classe seria "Comando", teríamos, pois, uma classe de atribuição, além de o comando while e o comando composto - constituído por uma lista de outros comandos normalmente delimitados por indicadores como { e } ou palavras reservadas como begin, end, etc. Normalmente são representados utilizando o padrão composite. Vide também o outro diagrama representado abaixo.

Vale ressaltar que é fundamental implementar cada método do objeto composto para ser aplicável à lista de objetos que possui. Logo, um objeto "cliente" faz necessário ativar o método desenha() de um objeto do tipo "Janela". Este método deverá ser capaz de ativar o desenha() de objeto que o contém. Desta maneira, será possível interagir com uma composição de objetos da mesma forma que se interage com objetos individuais.

Estrutura[editar | editar código-fonte]

O diagrama abaixo mostra a estrutura de classes do exemplo de componentes gráficos apresentada acima.

Hierarquia de Classes para Elemento Gráfico

Representa-se, no diagrama de classes acima, a estrutura do padrão composite, sempre composta por uma estrutura auto-referenciada.

O diagrama abaixo mostra a hierarquia de classes pertencente ao exemplo de comandos de linguagem de programação hipotética apresentada.

Hierarquia de Classes para Comando

Elementos do Padrão[editar | editar código-fonte]

Estrutura básica do padrão Composite

As classes e objetos participantes nesse padrão são:

  • Componente
    • Declara a interface para objetos nessa composição.
    • Implementa o comportamento padrão para a interface comum à todas as classes.
    • Declara uma interface para acessar os componentes-filho.
    • (Opcional) - Define uma interface para acessar os componentes-pai na estrutura recursiva, e a implementa se for apropriado.
  • Folha
    • Representa o objeto folha na composição. A folha não tem nenhum componente-filho.
    • Define o comportamento para objetos primitivos na composição.
    • Herda todos os métodos de Component porém só implementa de fato os que lhe interessam,neste caso o método Operation, nos outros são inseridos exceções que serão geradas em tempo de execução.
  • Composite
    • Define o comportamento para componentes que possuam componentes-filho.
    • Armazena componentes-filho.
    • Implementa funções relacionadas aos componentes-filho na interface do Componente.
  • Cliente
    • Manipula objetos da composição através da interface do Componente.

Exemplo de Aplicação Prática do Padrão em UML[editar | editar código-fonte]

Diagrama de classes UML mostrando uma implementação do padrão Composite.

Imagine que você está fazendo um sistema de gerenciamento de arquivos. Como você já sabe é possível criar arquivos concretos (vídeos, textos, imagens, etc.) e arquivos pastas, que armazenam outros arquivos. O problema é o mesmo, como fazer um design que atenda estes requerimentos?

Utilizando o padrão Composite[editar | editar código-fonte]

A ideia do Composite é criar uma classe base que contém toda a interface necessária para todos os elementos e criar um elemento especial que agrega outros elementos.

A classe base Arquivo implementa todos os métodos necessários para arquivos e pastas, no entanto considera como implementação padrão a do arquivo, ou seja, caso o usuário tente inserir um arquivo em outro arquivo uma exceção será disparada. Note como a classe ArquivoVideo não tem métodos.

Já na classe que representa a pasta (ArquivoComposite) nós sobrescrevemos o comportamento padrão e repassamos a chamada para todos os arquivos, sejam arquivos ou pastas.

A primeira vantagem, e talvez a mais forte, seja o fato de os clientes do código Composite serem bem simplificados, pois podem tratar todos os objetos da mesma maneira.

Exemplo de Uso[editar | editar código-fonte]

O exemplo a seguir, escrito em Java, implementa uma classe gráfica, na qual, pode ser uma elipse ou uma composição de diversas outras formas geometrias, que, todas podem ser representadas no gráfico.

Ele pode ser estendido para implementar diversos outras formas geográficas (círculo, quadrado, etc.) no gráfico.

Java[editar | editar código-fonte]

/** "Component" */
interface Graphic {

    //Printa o grafico.
    public void print();
}

/** "Composite" */
import java.util.List;
import java.util.ArrayList;
class CompositeGraphic implements Graphic {

    //Coleção de Graficos  filhos
    private List<Graphic> childGraphics = new ArrayList<Graphic>();

    //Printa o grafico
    public void print() {
        for (Graphic graphic : childGraphics) {
            graphic.print();
        }
    }

    //Adiciona o grafico  a composição.
    public void add(Graphic graphic) {
        childGraphics.add(graphic);
    }
    //Remove a forma geometrica da composição.
    public void remove(Graphic graphic) {
        childGraphics.remove(graphic);
    }
}

/** "Leaf" */
class Ellipse implements Graphic {

    //Printa o grafico.
    public void print() {
        System.out.println("Ellipse");
    }
}

/** Client */
public class Program {

    public static void main(String[] args) {
        //Inicializa quatro elipses
        Ellipse ellipse1 = new Ellipse();
        Ellipse ellipse2 = new Ellipse();
        Ellipse ellipse3 = new Ellipse();
        Ellipse ellipse4 = new Ellipse();

        //Inicializa tres componentes do grafico.
        CompositeGraphic graphic = new CompositeGraphic();
        CompositeGraphic graphic1 = new CompositeGraphic();
        CompositeGraphic graphic2 = new CompositeGraphic();

        //Faz o grafico
        graphic1.add(ellipse1);
        graphic1.add(ellipse2);
        graphic1.add(ellipse3);

        graphic2.add(ellipse4);

        graphic.add(graphic1);
        graphic.add(graphic2);
       // Printa quatro vezes a String Ellipse ( Ele printa o grafico completo).
        graphic.print();
    }
}

Exemplo Simples[editar | editar código-fonte]

/// Trata elementos como uma composição de um ou mais elementos, de forma que os componentes 
/// possam ser separados entre si.
public interface IComposite
{
    void CompositeMethod();
}

public class LeafComposite :IComposite
{
    public void CompositeMethod()
    {
        //Faz algo. 
    }
}

/// Elementos de IComposite podem ser separados dos outros 
public class NormalComposite : IComposite
{
    public void CompositeMethod()
    {
        //Faz alguma coisa. 
    }

    public void DoSomethingMore()
    {
        //Faz outra coisa. 
    }
}

C++

# include <iostream>
# include <vector>
using namespace std;

// 2. criando uma "interface" (Minimo demominador comum)
class Componente
{
  public:
    virtual void traverse() = 0;
};

class Folha: public Componente
{
    // 1. Escalar classe
    int valor;
  public:
    Folha(int val)
    {
        valor = val;
    }
    void traverse()
    {
        cout << valor << ' ';
    }
};

class Composite: public Componente
{
    // 1. classe Vetor
    vetor < Componente * > filho; // 4. "conteiner" Acoplado à interface
  public:
    // 4. "conteiner" classe acoplado à interface
    void add(Componente * ele)
    {
        filho.push_back(ele);
    }
    void traverse()
    {
        for (int i = 0; i < filho.size(); i++)
        // 5.  Polimorfismo para delegar ao filho
          filho[i]->traverse();
    }
};

int main()
{
  Composite conteiners[4];

  for (int i = 0; i < 4; i++)
    for (int j = 0; j < 3; j++)
      conteiners[i].add(new Folha(i *3+j));

  for (i = 1; i < 4; i++)
    conteiners[0].add(&(conteiners[i]));

  for (i = 0; i < 4; i++)
  {
    conteiners[i].traverse();
    cout << endl;
  }
}

JavaScript[editar | editar código-fonte]

var Node = function (name) {
    this.children = [];
    this.name = name;
}

Node.prototype = {
    add: function (child) {
        this.children.push(child);
    },

    remove: function (child) {
        var length = this.children.length;
        for (var i = 0; i < length; i++) {
            if (this.children[i] === child) {
                this.children.splice(i, 1);
                return;
            }
        }
    },

    getChild: function (i) {
        return this.children[i];
    },

    hasChildren: function () {
        return this.children.length > 0;
    }
}

// recursively traverse a (sub)tree

function traverse(indent, node) {
    log.add(Array(indent++).join("--") + node.name);

    for (var i = 0, len = node.children.length; i < len; i++) {
        traverse(indent, node.getChild(i));
    }
}

// logging helper

var log = (function () {
    var log = "";

    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { alert(log); log = ""; }
    }
})();

function run() {
    var tree = new Node("root");
    var left = new Node("left")
    var right = new Node("right");
    var leftleft = new Node("leftleft");
    var leftright = new Node("leftright");
    var rightleft = new Node("rightleft");
    var rightright = new Node("rightright");

    tree.add(left);
    tree.add(right);
    tree.remove(right);  // note: remove
    tree.add(right);

    left.add(leftleft);
    left.add(leftright);

    right.add(rightleft);
    right.add(rightright);

    traverse(1, tree);

    log.show();
}

Implementação[editar | editar código-fonte]

Para implementar o padrão Composite, é necessário considerar vários aspectos, sendo eles:

  • Referências explícitas aos pais: Manter referências dos componentes-filhos para seus pais pode simplificar o percurso e a administração de uma estrutura composta, ou seja, caso seja necessário mover-se para cima ou deletar um componente, isso seria simplificado.
  • Compartilhamento de Componentes: Seria reduzido os requisitos de espaço de armazenamento. Porém, quando um componentes não pode ter mais do que um pai, o compartilhamento de componentes torna-se difícil.
  • Maximização da interface de Component: Torna os clientes desconhecedores das classes específicas que estão usando.
  • Declarando as operações de gerenciamento de filhos:
    • Definir a interface de gerenciamento dos filhos na raiz da hierarquia de classes lhe dá transparência porque você pode tratar todos os componentes uniformemente. No entanto, isso diminui a segurança porque os clientes podem tentar fazer coisas sem sentido, como acrescentar e remover objetos das folhas.
    • Definir o gerenciamento de filhos na classe Composite lhe dá segurança porque qualquer tentativa de acrescentar ou remover objetos das folhas será detectada em tempo de compilação em uma linguagem com tipos estáticos, como C++. Porém, você perde transparência porque as folhas e os compostos têm interfaces diferentes.
  • Uso de caching para melhorar o desempenho: Caso seja necessário percorrer ou pesquisar composições com frequência, a classe composite pode fazer um cache de informações sobre seus filhos para poder realizar a navegação, fazendo com que tenha uma abreviação do percurso ou a busca.
  • Garbage Collector: Caso utilizado em linguagens sem garbage collection, o Composite seria o responsável pela deleção de seus filhos, quando esses forem destruídos.

Consequências do Uso[editar | editar código-fonte]

  • Objetos complexos podem ser compostos de objetos mais simples recursivamente. O cliente pode tratar objetos simples ou compostos de maneira uniforme uma vez que é definida uma interface comum (Component) à ambos.
  • Facilita a adição de novos componentes: o cliente não tem que mudar com a adição de novos objetos (simples ou compostos).
  • Do lado negativo, o projeto fica geral demais.
    • É mais difícil restringir os componentes de um objeto composto;
    • O sistema de tipagem da linguagem não ajuda a detectar composições erradas.

Relação com Iterator[editar | editar código-fonte]

O padrão Composite é bastante utilizado com o Iterator para percorrer a estrutura em forma de árvore oferecida pelo primeiro. Imaginemos que há uma classe abstrata Component com vários métodos e um deles é utilizado para imprimir toda estrutura. Esse método printar() é herdado tanto pela folha quanto pelo composite, portanto fazendo essa iteração é possível percorrer e imprimir todos os componentes de Component pois tanto a folha quanto o composite são do tipo Component e implementam suas própria versões do método printar().

  public class Composite extends Component {
     ArrayList<Component> components = new ArrayList<Component>();

     String componentName;
      
     // alguns métodos herdados por Component
     public void printar(){
        System.out.println("Componente :" + componentName);

        Iterator iterator = components.iterator();
        while(iterator.hasNext()){
            Component component = (Component)iterator.next();
            component.printar();
        }
     }
  }

Bibliografia e Referências[editar | editar código-fonte]

Ver também[editar | editar código-fonte]