C# -  Padrões Estruturais Gof - Composite


 Neste artigo vou apresentar com o padrão estrutural Gof Composite.

O padrão de projeto Composite compõe objetos em estruturas de árvore para representar hierarquias parte-todo.

Esse padrão permite que os clientes tratem objetos individuais e composições de objetos uniformemente compondo objetos em uma estrutura de árvore e, em seguida, trabalhar com essa estrutura como se fosse um único objeto.

O uso desse padrão de design faz sentido o problema que queremos resolver pode ser representado como uma estrutura semelhante a uma árvore. Sendo que a aplicação do padrão Composite permite executar métodos recursivamente em toda astrutura da árvore resumindo os resultados.

Como exemplo de aplicação do Composite temos o seguinte cenário :

Para ilustrar vamos pensar nas partes de um computador

Um computador é feito de várias partes ou elementos integrados entre si, conforme mostrado na figura (de forma bem resumida)

 

Como mostra a imagem temos vários objetos que juntos formam o computador: o Gabinete, Periféricos, Disco rígido, Mouse, Teclado, CPU, Memória, etc., tudo são objetos.

Neste contexto um objeto Composite ou objeto composto(fazendo a tradução livre) é um objeto que contem outros objetos. E devemos lembrar que um objeto Composite também pode conter outros objetos compostos criando um hierarquia de objetos. Quando um objeto não contiver nenhum outro objeto ele é simplesmente tratado como um objeto primitivo ou Leaf
que numa tradução livre seria a Folha.

Assim aplicando este conceito ao nosso exemplo podemos identificar os Composite object (objetos compostos por outros objetos) como sendo: Computador, Gabinete, Periféricos e Placa Mãe.

E podemos identificar os objetos folha ou Leaf object (objetos primitivos ou folha que não são compostos por outros objetos) como sendo: Disco Rígido, Mouse, Teclado, CPU e RAM

Destacando que todos os objetos são do mesmo tipo ou seja no exemplo eles são dispositivos eletrônicos.

Desta forma o padrão de projeto Composite terá uma estrutura semelhante a uma árvore  com objetos compostos ou composite objects e objetos folha ou leaf objects.

E aqui é importante destacar como padrão atua;  a ideia fundamental é que, se você realizar alguma operação no objeto folha, a mesma operação poderá ser realizada nos objetos compostos.

Por exemplo, se consigo obter o preço de um Mouse, devo ser capaz de obter o preço dos Periféricos e até mesmo devo ser capaz de obter/calcular o preço do objeto Computador.

Percebeu a navegação na hierarquia ????

E para implementar este comportamento devemos usar o Padrão de projeto Composite.

Estrutura do padrão Composite

Desta forma na estrutura do padrão Composite podemos identificar 3 componentes:

1- Component - Um Component é uma interface que descreve operações comuns a elementos simples ou complexos da árvore.

2- Leaf - Um Leaf ou Fôlha é um único objeto, que não possui subelementos.

3- Composite - Composite é um objeto que possui subelementos (folhas ou outros objetos compostos)   

Diagrama UML

A seguir temos o diagrama UML do padrão Composite segundo o Gof:

Os participantes são:

1- Component - É Esta é a interface ou a classe abstrata que define as funções comuns que precisam ser implementadas nos objetos primitivos (Leaf) e compostos(Composite)

O Component serve como o tipo base para todos os objetos na hierarquia.

2- Leaf - É a classe que implementa os comportamentos padrão conforme definido por sua interface Component. A Leaf não contém uma referência a nenhum tipo composto.

3- Composite - Contém outros objetos compostos e implementa os métodos de Component. Esta classe contém métodos relacionados ao gerenciamento de objetos filhos.

É uma classe concreta que define as operações necessárias que podem ser realizadas nos componentes filhos, ou seja, os métodos Adicionar, Remover, Obter, Encontrar, etc.

O objeto Composite não está familiarizado com as classes concretas de seus filhos. Ele se comunica com seus filhos por meio da interface Component.


4- Client -  Usa a interface Component para acessar tipos compostos e executar as operações definidas de maneira uniforme em toda a hierarquia.

Usos do padrão Composite

Podemos usar o padrão Composite quando :

  1. Quando precisarmos criar uma estrutura em árvore para resolver um problema;
  2. Quando precisarmos representar hierarquias todo-parte de objetos (um uso comum é representar a interface do usuário) ;
  3. Queremos que os clientes ignorem a diferença entre composições de objetos e objetos individuais;

Vantagens e desvantagens do padrão Bridge

As vantagens em usar o padrão são:

  1. O padrão ajuda a alcançar a uniformidade (uso de funções semelhantes) em toda a hierarquia de objetos que contém tipos de objetos primitivos e compostos;
     
  2. O padrão torna mais fácil para o cliente atingir a funcionalidade desejada sem se preocupar com o tipo de objeto com o qual está lidando;

Como desvantagens do padrão vale destacar que a sua aplicação pode se tornar muito genérica devido à sua uniformidade, tornando difícil restringir objetos que podem ser incluídos no grupo composto.

Aplicando o padrão Composite

Veremos agora um exemplo prático de aplicação do padrão Composite.

Vamos supor que temos uma organização com diferentes departamentos, onde cada departamento possui um conjunto de funcionários, e, que precisamos coletar (ler) as horas trabalhadas registradas de cada funcionário em cada departamento, o total de horas trabalhadas por cada departamento e o total geral de horas trabalhadas na organização.



Podemos identificar aqui uma hierarquia de objetos consistida de:

1- Organização (Composite)
2- Departamentos (Composite)
3- Funcionários (Leaf)


Aqui podemos identificar a Organização como sendo o composite que é e composto de outros objetos e Funcionários como sendo o Leaf pois não contem subelementos.

Assim vamos aplicar o padrão Composite para resolver o problema proposto.

Para isso vamos criar uma aplicação Console no ambiente do .NET 5.0 usando o VS 2019.  A seguir temos o diagrama de classes que foi gerado pela implementação feita no VS 2019:



1- Classe HoraTrabalhada :  Representa o Component, sendo a classe base que declara a funcionalidade comum GetHoraTrabalhada para os objetos Funcionario (Leaf) e Organizacao (Composite).  A classe também contém os métodos Add e Remove que poderão ser implementados;

2- A classe Funcionario :  Representa o Leaf ou Folha na estrutura da árvore que não pode ter filhos.  Esta classe implementa a classe abstrata HoraTrabalhada  e substitui o método GetHoraTrabalhada() para retornar a hora de trabalho registrada para o funcionário;

3- A classe Organizacao :  Representa o Composite e os objetos complexos (compostos) que podem ter Funcionario (folha)
e outro composto como seus filhos;

A seguir temos o código usado na implementação:

1- Classe abstrata HoraTrabalhada (Component)

using System;

namespace Composite1.Component
{
    //representa o Component
    public abstract class HoraTrabalhada
    {
        public HoraTrabalhada()
        { }
        public string Nome { get; set; }
        public abstract int GetHoraTrabalhada();
        public virtual void Add(HoraTrabalhada component)
        {
            throw new NotImplementedException();
        }
        public virtual void Remove(HoraTrabalhada component)
        {
            throw new NotImplementedException();
        }
    }
}

2- Classe Funcionario (Leaf)

using Composite1.Component;
using System;

namespace Composite1.Leaf
{
    //Representa o Leaf
    public class Funcionario : HoraTrabalhada
    {
        public int Id { get; set; }
        public int Horas { get; set; }
        public override int GetHoraTrabalhada()
        {
            Console.WriteLine($"O Funcionário {Id}-{Nome} registrou {Horas} trabalhadas");
            return Horas;
        }
    }
}

3- Classe Organizacao (Composite)

using Composite1.Component;
using System;
using System.Collections.Generic;

namespace Composite1.Composite
{
    //representa o Composite
    public class Organizacao : HoraTrabalhada
    {
        protected List<HoraTrabalhada> departamentos = new List<HoraTrabalhada>();

        public override void Add(HoraTrabalhada component)
        {
            departamentos.Add(component);
        }
        public override void Remove(HoraTrabalhada component)
        {
            departamentos.Remove(component);
        }

        /// funcionalidade comum
        public override int GetHoraTrabalhada()
        {
            var horasTrabalhadasDepartamento = 0;
            foreach (var departamento in departamentos)
            {
                horasTrabalhadasDepartamento += departamento.GetHoraTrabalhada();
            }
            Console.WriteLine($"{Nome} registrou um total de [{horasTrabalhadasDepartamento}] horas\n");
            return horasTrabalhadasDepartamento;
        }
    }
}

4- Program (Client)

using Composite1.Composite;
using Composite1.Leaf;
using System;

namespace Composite1
{
    //Client
    class Program
    {
        static void Main(string[] args)
        {
            Organizacao organizacao = new Organizacao { Nome = "JcmSoft Inc." };
            //
            Organizacao departamentoTI = new Organizacao { Nome = " TI " };
            departamentoTI.Add(new Funcionario { Id = 1001, Nome = "Maria", Horas = 8 });
            departamentoTI.Add(new Funcionario { Id = 1002, Nome = "Miguel", Horas = 6 });
            departamentoTI.Add(new Funcionario { Id = 1005, Nome = "Samuel", Horas = 8 });
            //
            Organizacao departamentoContabilidade = new Organizacao { Nome = " Contabilidade " };
            departamentoContabilidade.Add(new Funcionario { Id = 1003, Nome = "Beatriz", Horas = 7 });
            departamentoContabilidade.Add(new Funcionario { Id = 1004, Nome = "Paulo", Horas = 5 });
            //
            organizacao.Add(departamentoTI);
            organizacao.Add(departamentoContabilidade);
            organizacao.GetHoraTrabalhada();
            //
            Console.ReadKey();
        }
    }
}

A execução do projeto irá apresentar o seguinte resultado:

Mesmo que esse padrão possa parecer um pouco complexo, ele pode ser muito benéfico quando temos estruturas de árvore complexas em nosso código.

Além disso, por ter uma única interface que é compartilhada por todos os elementos no padrão Composite, o cliente não precisa se preocupar com a classe concreta com a qual trabalha.


Pegue o código do projeto aqui :   Composite1.zip

Referências:


José Carlos Macoratti