Design Patterns: Usando Composite para montar uma estrutura em árvore

Blz, pessoal

Já tem algum tempo que não escrevo sobre design patterns. Vou falar neste post de um padrão que utilizei no meu projeto atual, conhecido por Composite.

A ideia desse padrão é montar uma árvore onde tanto as folhas (objetos individuais) quanto os compostos (grupos de objetos) sejam tratados de maneira igual. Em termos de orientação a objetos, isso significa aplicarmos polimorfismo para chamar métodos de um objeto na árvore sem nos preocuparmos se ele é uma folha ou um composto.

Vamos seguir com a teoria básica e alguns exemplos de uso.

INTENÇÃO

“Compor objetos em estruturas de árvore para representarem hierarquias parte-todo. Composite permite aos clientes tratarem de maneira uniforme objetos individuais e composições de objetos.” (Design Patterns: Elements of Reusable Object-Oriented Software)

ESTRUTURA/EXEMPLO

Suponha que tenhamos um questionário formado de questões, que podem estar agrupadas em blocos e blocos podem conter outros blocos. Podemos então ter a estrutura a seguir para um questionário:

Bloco A
—– Q1
—– Q2
—– Q3
Bloco B
—– Bloco B1
———- Q4
———- Q5
—– Bloco B2
———- Q6
—– Q7

Além da flexibilidade mostrada acima, queremos um meio fácil de exibir essa estrutura em tela/página, sem ter que nos preocupar se estamos exibindo um bloco ou uma questão.

Neste cenário o Composite se encaixa perfeitamente e pode ser ilustrado pela estrutura abaixo:

composite

Dentro da estrutura do Composite, temos 3 participantes:

O Componente (ElementoDoQuestionario): é a interface que define métodos comuns às classes dentro da composição e, opcionalmente, define uma interface para acessar o objeto “pai” de um componente.

A Folha (Questao) é um componente que, como o nome indica, não possui filhos (está nas extremidades da árvore).

O Composto (Bloco) é um componente que, como o nome indica, é composto de outros componentes, que podem ser folhas ou outros compostos.

Notem no questionário acima que o Bloco B possui 3 filhos: 2 blocos (B1 e B2) e uma questão (Q7). Por sua vez, B1 possui 2 filhos (Q4 e Q5) e B2 possui um filho (Q6).

Vejamos uma forma de implementar o padrão:


 // classe base (o componente)
 public abstract class ElementoDoQuestionario
 {
     protected string Descricao;

     protected ElementoDoQuestionario(string descricao)
     {
         Descricao = descricao;
     }
     public abstract void Exibir();
 }

 // bloco (o composto)
 public class Bloco : ElementoDoQuestionario
 {
     private IList<ElementoDoQuestionario> _elementos = new List<ElementoDoQuestionario>();
     public IEnumerable<ElementoDoQuestionario> Elementos { get { return _elementos; } }

     public Bloco(string descricao) : base(descricao) { }

     public override void Exibir()
     {
         Console.WriteLine("Bloco: {0}", Descricao);
         foreach (var elemento in _elementos)
             elemento.Exibir();
     }

     public void Adicionar(ElementoDoQuestionario elemento)
     {
         _elementos.Add(elemento);
     }

     public void Remover(ElementoDoQuestionario elemento)
     {
         _elementos.Remove(elemento);
     }
 }

 // questão (a folha)
 public class Questao : ElementoDoQuestionario
 {
     public Questao(string descricao) : base(descricao) { }

     public override void Exibir()
     {
         Console.WriteLine("Questão: {0}", Descricao);
     }
 }

É importante considerar o seguinte:

A interface de acesso e gerenciamento dos ‘filhos’ pertence somente ao composto (Bloco). Embora desta forma Questao e Bloco possuam interfaces diferentes, é mais seguro que somente Bloco gerencie elementos-filhos. Do contrário, teríamos Questao comportando-se como um bloco e tentando adicionar elementos-filhos a ela, o que não faz sentido.

Portanto, ficam na classe-base somente os métodos que devam comportar-se uniformemente. No exemplo, o método ‘Exibir’ será invocado para exibir um elemento do questionário, seja ele de que tipo for.

Também, por ser um exemplo didático, considere algumas “liberdades” tomadas:

  • Um elemento é exibido com um “tosco” Console.WriteLine(), para ser demonstrado em uma aplicação Console.
  • Um elemento possui apenas uma descrição. Uma questão poderia, por exemplo, saber exibir suas opções de resposta, caso fosse uma questão de múltipla escolha.

Continuando com o exemplo, montamos um questionário e mandamos exibi-lo:

 // montando estrutura do questionário com vários blocos aninhados e questões
 var blocoA = new Bloco("A) Formação Educacional");
 var questao1 = new Questao("Qual sua formação?");
 var questao2 = new Questao("Pretende fazer alguma pós-graduação?");

 blocoA.Adicionar(questao1);
 blocoA.Adicionar(questao2);

 var blocoB = new Bloco("B) Habilidades");
 var blocoB1 = new Bloco("B1) Habilidades Técnicas");
 var questao3 = new Questao("O que é SOLID?");

 blocoB1.Adicionar(questao3);
 blocoB.Adicionar(blocoB1);

 var blocoRaiz = new Bloco("Inicio");
 blocoRaiz.Adicionar(blocoA);
 blocoRaiz.Adicionar(blocoB);

 // exibindo toda a estrutura
 blocoRaiz.Exibir();

Por fim, rodando o código acima em uma aplicação Console, teremos o seguinte resultado:

composite-resultado

Tudo a partir de um mero “Exibir”. Legal, não é?

OUTRO EXEMPLO

O padrão Composite não é usado apenas em situações de exibição da estrutura, como no exemplo acima ou ainda no exemplo clássico de manipulação de um menu (que é composto de itens de menu e sub-menus).

Ele pode ser empregado, como dito em sua intenção, sempre que quisermos tratar de maneira igual objetos individuais e composições de objetos.

Vejamos uma situação onde departamentos são compostos de funcionários ou de outros departamentos e desejamos calcular o custo total de um ‘membro’ da empresa, seja ele departamento ou funcionário.

Poderíamos fazer algo como:


 public abstract class MembroDaEmpresa
 {
     public abstract decimal CalcularSalario();
 }

 public class Funcionario : MembroDaEmpresa
 {
     public override decimal CalcularSalario()
     {
         // return ALGUM-CALCULO-MAGICO
     }
 }

 public class Departamento : MembroDaEmpresa
 {
     private IList<MembroDaEmpresa> _membrosDaEmpresa = new List<MembroDaEmpresa>();
     public IEnumerable<MembroDaEmpresa> MembrosDaEmpresa { get { return _membrosDaEmpresa; } }

     public override decimal CalcularSalario()
     {
         return _membrosDaEmpresa.Sum(membro => membro.CalcularSalario());
     }
 }

Observem no exemplo que o Funcionario (folha) sabe calcular seu próprio salário com as informações que ele próprio possui (omitido no código). Já Departamento (composto) calcula seu salário pedindo para todos os seus membros (que podem ser outros departamentos ou funcionários) retornarem os seus salários. Exatamente a mesma ideia do exemplo do questionário.

CONCLUSÃO

Neste artigo, foi mostrado o design pattern Composite com dois exemplos de uso do mesmo. Este padrão nos ajuda a tratar uniformemente elementos únicos e composições de elementos.

O que acharam?

Até a próxima!

Anúncios

4 comentários em “Design Patterns: Usando Composite para montar uma estrutura em árvore

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s