Delegates: Cuidando da sua arquitetura

Publicado por José Luz
30/8/2011
Categoria:
Tags: , ,

Em um projeto de múltiplas camadas com comunicações e transações pesadas ocorrendo em grande quantidade, evitar a perda do controle de uma User Interface(UI) é uma tarefa que exige chamadas assíncronas. Porém chamadas assíncronas criam dependência entre as camadas, o que é um grande problema tratado de muitas formas. Só que algumas dessas soluções podem quebrar a arquitetura e as camadas do projeto, vou discutir neste artigo o uso de delegate do C#.NET e Events como alternativa para esta solução.

Para evitar uma dependência da UI com as outras camadas de negócio podemos combinar um delegate personalizado com eventos. Esta é uma solução elegante e muito interessante, senão a melhor, que o C#.NET nos oferece para implementarmos um pattern tão utilizado quanto o MVC.

Analisando a implementação

O objetivo é que o evento ao ser lançado seja pertinente com os acontecimentos que este representará. Vale lembrar que eventos são declarados utilizando delegates, o que torna qualquer delegate apto a ter seus parâmetros formais como assinatura para lançar o evento. Este é o comportamento que buscamos para alcançar o objetivo traçado.

Em resumo, podemos lançar um evento que terá os parâmetros personalizados, criando o evento com a “tipagem” do novo delegate personalizado, vide o exemplo a seguir:

exemplo 1. Declarando o delegate e o evento com a “tipagem” do delegate:

public class MyQueue<T>
{
    public delegate void CurrentDoneHandler(T currentElement, int elementsRemained);
    public event CurrentDoneHandler ElementRemoved;
}

Agora já criamos o evento com a assinatura do delegate CurrentDoneHandler, vamos lançá-lo no exemplo 2. Repare nos parâmetros que o evento ElementRemoved precisa para ser lançado.

exemplo 2. Disparando o evento conforme o padrão do delegate descrito anteriormente:

public void OnElementRemoved(T currentElement, int elementsRemained)
{
    if (ElementRemoved != null)
        ElementRemoved(currentElement, elementsRemained);
}

Talvez você pode estar se perguntando “o que isso altera, afinal de contas, na minha arquitetura?”, certo? Bem, o impacto na organização é surpreendente, é possível destacar como maior vantagem o fim das dependências cíclicas, além de evitar a comunicação de camadas superiores ou de borda com camadas inferiores. Outra vantagem é o desacoplamento da UI com as camadas de negócio, o que permite substituir a UI sem afetar ou ser necessário alterar outras camadas.

Além disso, este modo simplifica a comunicação da view com o controller, já que em geral, após disparar um evento sem delegate personalizado é necessário que o controller acesse a view para obter as informações que ele irá processar - pattern MVP. Já com o delegate personalizado o evento já passa pro controller tudo que ele precisa para cumprir sua responsabilidade, chamando a view somente para atualiza-lá com as informações processadas. No próximo tópico, essa teoria se torna aplicação real.

Aplicando o delegate

Agora vamos voltar ao nosso problema inicial: Como efetuar chamadas assíncronas de UI com o sistema, sem criar dependência estrutural entre elas e ainda impedir a perda de controle da UI.

Para começar vamos adotar um sistema de camadas, onde haverá a camada de UI que chamaremos de View. Também teremos uma camada que servirá como controle da View, que chamaremos de Control ou Controller. Nós iremos focar nossos exemplo nessas duas camadas a princípio. Vide o modelo de camadas que trabalharemos:

Iremos aplicar o MVC, criando interfaces na camada Control que serão implementadas na camada View. Na camada Control também registraremos os listeners que serão responsáveis por tratar os eventos que a UI disparará. Nosso exemplo será uma tela de separação de materiais que permite separar todos os materiais de um pedido, consultar as quantidades em estoque e olhar outros pedidos do mesmo cliente. Portanto teremos uma Web UI aproximadamente assim:

System UI

Temos três funções nessa tela: “Separar”, “Mostrar/Ocultar pedidos desse cliente”(Show Orders From Client) e “Mostrar/Ocultar quantidades do meu estoque”(Show Stock Quantities). À princípio o sistema não irá carregar as últimas duas funções na abertura da tela e sim somente quando o usuário solicitar, evitando consultas desnecessárias ao sistema. Vou me focar durante as próximas linhas na implementação dessas duas funções.

Vamos declarar a interface dessa UI. Ela será declarada na camada Controller. Logo teremos uma interface chamada ISeparateItensView que deve atender todas as funcionalidades que a UI necessitará para executar as funções que iremos detalhar.

Exemplo 2.Declarando os delegate em uma estrutura específica

public class SeparateItensStruct
{
    public delegate void ShowStockQuantitiesHandler(IList<OrderItem> item);
    public delegate void ShowClientOrdersHandler(Client client);
    //Other stuffs here.
}

Exemplo 3. Declarando a interface ISeparateItensView.

public interface ISeparateItensView
{
    event SeparateItensStruct.ShowClientOrdersHandler ShowClientOrdersDetails;
    event SeparateItensStruct.ShowStockQuantitiesHandler ShowStockQuantitiesDetails;

    void InitViewWith(Order order);
    void SetItemList(IList<Item> itens);
    void ShowOrderDetail(Order order);
    void ShowStockQuantity(IList<StockItem> products);
    void ShowClientOrders(IList<Order> orders);
}

Reparem que no exemplo 2 declarei os dois delegates e em seguida no exemplo 3 assinei os eventos com estes, também declarei na interface algumas operações que são necessárias para meu controle manipular a view.

Já na camada View, por sua vez, teremos a implementação da interface declarada acima e a chamada dos eventos no Web Form SeparateItemView.

Exemplo 4. Implementando a interface ISeparateItensView:

public partial class SeparateItemView : System.Web.UI.Page, ISeparateItensView
{
      #region ISeparateItensView Members
      public event SeparateItensHandlers.ShowClientOrdersHandler ShowClientOrdersDetails;
      public event SeparateItensHandlers.ShowStockQuantitiesHandler ShowStockQuantitiesDetails;
      public void SetItemList(IList<Item> itens)
      {
          //Set Itens at GridView
      }

      public void InitViewWith(Order order)
      {
          //Starts view details with Order Information.
      }

     public void ShowOrderDetail(Order order)
     {
          //Show order details.
     }

     public void ShowStockQuantity(IList<StockItem> products)
     {
          //Show Stock Quantity
     }

     public void ShowClientOrders(IList<Order> orders)
     {
         //Show Client Orders GridView.
     }
     #endregion

     private void OnShowClientOrders(Client client)
     {
         if (ShowClientOrdersDetails != null)
             ShowClientOrdersDetails(client);
     }

     private void OnShowStockQuantities(IList<OrderItem> itens)
     {
          if (ShowStockQuantitiesDetails != null)
               ShowStockQuantitiesDetails(itens);
     }

     private void ShowOtherOrdersFromClientButton_Click(object sender, EventArgs e)
     {
          OnShowClientOrders((Client)HttpContext.Current.Session["Client"]);
     }

      private void ShowStockQuantitiesButton_Click(object sender, EventArgs e)
      {
          OnShowStockQuantities((Order)HttpContext.Current.Session["Order"]);
      }
     // Other view code.
}

Deixando os detalhes de como a implementação da view ocorrerá, vamos analisar agora o código que o controle terá para executar suas funções que escutará os eventos que a UI poderá lançar.

Este código ficará na camada Contoller e será responsável por cuidar somente de uma view que implemente a interface ISeparateItensView, o que no nosso caso é a SeparateItensView.

exemplo 5. Declarando o controller SeparateItemControl.

public class SeparateItemControl
{
    public SeparateItemControl(ISeparateItensView view)
     {
         separateItensView = view;
         RegisterEvents();
         //Other Code here...
     }

     public void RegisterEvents()
     {
          separateItensView.ShowClientOrdersDetails += (client) => ShowClientOrders(client);
          separateItensView.ShowStockQuantitiesDetails += (itens) => ShowOrderDetails(itens);
     }

     public void ShowClientOrders(Client client)
     {
          separateItensView.ShowClientOrders(separateService.GetOrderByClient(client));
     }

     public void ShowOrderDetails(IList<OrderItem> orderItens)
     {
          separateItensView.ShowStockQuantity(stockService.GetStockQuantities(orderItens));
     }
}

Aqui vemos o construtor da classe SeparateItemControl receber como parâmetro uma interface ISeparateItensView e em seguida registrar os eventos que esta view pode lançar. Logo temos dois métodos, que respeitam a assinatura do delegate definido na interface ISeparateItensView, os métodos: ShowOrderDetails e ShowClientOrders.

Serão estes métodos que serão executados quando o evento for disparado. Nesta camada os métodos tem a responsabilidade de buscar as informações que cada ação exige e atualizar a UI com as informações atualizadas.

Agora temos em mãos uma UI e um controller onde este não conhece estruturalmente a UI. O controller somente conhecerá a UI dinamicamente e ainda conta com o auxilio de Events.

Os perigos do uso de delegate

A utilização de delegates deve ser feita com muita cautela. Utilizar delegates vai causar dependência implícita e cabe ao desenvolvedor cuidar disso. Mas este é um assunto para um próximo artigo, por hora basta saber que encontramos justamente nessa dependência implícita a solução para obter a resposta das camadas inferiores sem ter que aguardar por uma resposta do metódo que, por vezes, pode ser demorada.

Conclusão

Também poderiamos repetir o processo de eventos nas camadas inferiores, fazendo o sistema ser muito mais desacoplado das implementações, criando interfaces para todos os processos e utilizando delegates e eventos para todas as chamadas.Porém, essa estrutura não é recomendada pois haverá um excesso de dependências implícitas, conforme descrito no trecho perigos do uso de delegates.

Claro que um sistema altamente desacoplado é uma situação muito boa, quase utópica, sendo possível substituir completamente uma camada sem afetar o código das outras. Porém utilizar delegates e eventos para atingir esse objetivo gera uma cadeia de eventos, que tem um alto custo de arquitetura, manutenção e eventualmente de desempenho. Utilizar Spring ou outra solução provavelmente será melhor para este caso.

Ainda sim o uso de delegates + Events é muito elegante, arquiteturalmente falando, simples de se implementar e com os devidos cuidados pode ser utilizado amplamente para atender o MVC ou outra necessidade que você encontre. Sem dúvida essa combinação é mais simples e fácil de ler em relação à implementação de interfaces que o JAVA exige para efetuar uma solução similar.

2 Comentários

  • Reply

    Por Ronaldo em 30 de August de 2011 às 17:52

    Discussão interessante, mas…
    Eu discordo que seja mais fácil, pensar, desenvolver, manter, alterar e etc… essa tal “combinação” (delegate/event) do que uma simples interface que não passa de uma definição de contratos. Usando o Spring (como sugeriu) é muito simples manter o baixo acoplamento em uma app, pelo mecanismo de injection, fora a facilidade que o framework oferece para controlarmos esse mecanismo. Usando o MAVEN e as estruturas de projetos “sugeridas” por ele, é fácil, separar os módulos de um projeto para que sejam utilizados em outros projetos e simplesmente disponibilizar um pacote “API” ou “Common” com o necessário para qualquer “frontend” ter uma boa comunicação com o “backend” sem “quebrar” camadas.

  • Reply

    Por Alexandre Cunha em 28 de September de 2011 às 1:00

    Concordo, sem dúvida o uso de interfaces com IoC (Spring) gera um desacoplamento mais “arquitetural”. Acho que o ponto do Zé foi tentar mostrar que em alguns casos o delegate simplifica alguns desacoplamentos, por exemplo no uso de eventos. Essa com certeza é uma dificuldade conhecida do Swing, registrar o evento do click de um botão por exemplo. É um exemplo que ao invés de ter que implementar uma interface só para sobrescrever o método onClick, basta passar um delegate. Agora de forma geral, falando de arquitetura, sem dúvida o buraco é mais embaixo, como você mesmo explicou.





Desenvolvido por hacklab/ com WordPress