De que se trata o artigo

O objetivo deste artigo é discutir o que são interfaces fluentes. Isto será feito através da apresentação dos benefícios oferecidos por este conceito, o qual fornece as bases necessárias para a construção de estruturas que conduzam a um código-fonte menos extenso e mais simplificado.


Em que situação o tema é útil:

Interfaces fluentes pode ser um recurso de grande valia na elaboração de classes que resultem na obtenção de um código mais simplificado. Métodos executados sequencialmente a partir de um objeto e que se espalhariam por diversas linhas, podem quando o tipo em uso implementar este conceito, vir a ser encadeados em uma única instrução. Dive rsos frameworks de larga aceitação no mercado empregam esta técnica: LINQ e o Fluent NHibernate constituem alguns exemplos.

Interfaces Fluentes

Projetando classes para um código-fonte mais simplificado A construção de classes básicas com funcionalidades consumidas por toda uma aplicação, corresponde a um tipo de ocorrência bastante comum. Em diversos casos, a implementação de uma operação envolverá a escrita de uma sequência de instruções envolvendo um objeto específico; tal situação pode produzir extensos blocos de comandos. Interfaces fluentes propõem alternativas para simplificar isto, buscando assim, a obtenção de um código mais enxuto e de rápida assimilação.

Os primeiros programas de computador nada mais eram do que um conjunto de instruções dispostas numa sequência lógica. Não eram incomuns trechos repetidos ao longo de um bloco código, assim como o uso extensivo de instruções desviando o fluxo de execução como resposta a determinadas condições (GOTOs). A este tipo de prática deu-se o nome de "código spaghetti". O desenvolvimento de aplicações segundo este modelo, se revelou mais adiante como uma tarefa trabalhosa, sobretudo, com o aumento da complexidade dos sistemas informatizados no decorrer do tempo.

A programação estruturada surgiu como uma solução às dificuldades desse estágio inicial do desenvolvimento de software. Estruturas de decisão e repetição, além da divisão de conjuntos de instruções relacionadas em sub-rotinas, foram alguns dos mecanismos introduzidos por este paradigma. Estas construções possibilitaram, através de uma melhor organização do código-fonte, a elaboração de poderosas aplicações atendendo aos mais variados objetivos.

Grandes avanços no hardware dos computadores também foram acompanhados, posteriormente, pelo advento de novas técnicas dentro da área de software. A Orientação a Objetos (OO) despontou como uma evolução da programação estruturada, dando uma grande ênfase à representação de elementos do mundo real como objetos formados por um agrupamento de características (atributos/propriedades) e comportamentos (operações). Tudo isso contribuiu, consequentemente, para uma melhor modelagem dos sistemas formulados em conformidade com tais conceitos.

Um dos grandes trunfos da Orientação a Objetos foi, sem sobra de dúvidas, o excelente trabalho efetuado por especialistas ao redor do mundo em torno de soluções a que se convencionou chamar de padrões de projetos (design patterns). Um pattern nada mais é do que um conjunto de recomendações que procura oferecer alternativas a uma demanda específica de desenvolvimento. Seguir diretivas deste tipo contribui para uma melhor estruturação das aplicações de software, assim como torna possível o reuso de um mecanismo de eficácia comprovada em projetos anteriores.

Padrões não devem ser encarados como uma garantia de obtenção de estruturas enxutas durante o processo de desenvolvimento de software. Muito embora as numerosas diretrizes fornecidas pela Orientação a Objetos levem à implementação de soluções seguindo boas práticas, ainda existirão trechos de código extensos e que executam operações sobre objetos através de uma lógica complexa.

Interfaces fluentes é uma maneira de simplificar a execução sequencial de funcionalidades disponibilizadas por um objeto. Trata-se de uma técnica que permite, basicamente, o agrupamento de um conjunto de chamadas a uma instância em uma única instrução. Classes concebidas segundo este padrão, dispensam os desenvolvedores de referenciar a todo o momento uma mesma estrutura, além de tornar mais conciso e legível o código-fonte que define um determinado comportamento.

A finalidade deste artigo é discutir de que maneiras classes OO dotadas de interfaces fluentes podem ser construídas, apresentando ainda diretrizes e benefícios decorrentes de tal abordagem. Com o objetivo de demonstrar este processo, será criada uma aplicação ASP.NET MVC de exemplo, a qual acessará um banco de dados SQL Server. O site em questão fará uso da implementação de um mecanismo de mapeamento objeto-relacional baseado em stored procedures e cuja principal classe conta com uma interface fluente. A biblioteca que contém estas funcionalidades de ORM também emprega expressões lambda (LINQ) para mapear o relacionamento entre campos de tabelas e propriedades de objetos, além de Reflection e recursos do ADO.NET.

Nota

Frameworks ORM ( “Object-relational mapping” - ou “Mapeamento objeto relacional”) permitem a representação de estruturas de bancos de dados sob a forma de classes. Alguns exemplos de mecanismos ORM populares são o Entity framework (o qual também integra a plataforma .NET) e o NHibernate (ferramenta gratuita baseada no Hibernate da plataforma Java).

Programação Fluente: uma visão geral

Interface fluente (ou em inglês “fluent interface”) é um termo criado pelos especialistas de software Martin Fowler e Eric Evans, sendo utilizado para descrever um padrão para a construção de classes que favorece a obtenção de um código menos extenso e mais legível. O acesso a diversas operações de um mesmo objeto, sucessivamente, é algo extremamente comum em atividades de desenvolvimento. Isto pode levar, à obtenção de extensos blocos de código e que impedem, devido a tal característica, uma rápida leitura e entendimento do cenário que se está considerando.

Sendo mais um pattern da Orientação a Objetos, o conceito de interface fluente não está restrito à plataforma .NET, podendo também se aplicar a estruturas concebidas em Java, por exemplo. Interfaces fluentes baseiam-se em uma técnica conhecida como "method chaining" (algo como "encadeamento de métodos", em português): caso diversas operações de um mesmo objeto precisem ser invocadas uma após a outra, a implementação de um tipo que permita encadear seus métodos torna possível a execução de tal processo por meio de uma única instrução.

Na Listagem 1 é apresentado um exemplo de classe que não implementa o conceito de interface fluente. Como pode ser observado, o tipo NotaFiscal conta com funcionalidades para o cálculo dos valores totais de uma nota fiscal, com a execução dos métodos correspondentes devendo acontecer em uma ordem específica a partir de uma instância de NotaFiscal (conforme indicado na Listagem 2).

Nota

Method chaining (ou "encadeamento de métodos") é uma técnica OO em que múltiplos métodos são invocados sucessivamente, a partir de uma instância inicial de um objeto. A fim de permitir que uma operação seja executada imediatamente após a outra, métodos que possibilitam este tipo de comportamento devolvem uma referência que aponta para o próprio objeto que se está manipulando naquele momento.

Listagem 1. Classe que não emprega o conceito de interface fluente


   ...
   
  public class NotaFiscal
  {
      public void CalcularVlTotalProdutos()
      {
          // Instruções para cálculo do valor
          // total dos produtos
   
          ...
      }
   
      public void CalcularVlBaseIPI()
      {
          // Instruções para cálculo do valor
          // da base do IPI
   
          ...
      }
   
      public void CalcularVlIPI()
      {
          // Instruções para cálculo do valor do IPI
   
          ...
      }
   
      public void AplicarDesconto(decimal percentual)
      {
          // Aplicar percentual de desconto sobre
          // o total dos produtos
   
          ...
      }
   
      public void CalcularVlBaseICMS()
      {
          // Instruções para cálculo do valor
          // da base do ICMS
   
          ...
      }
   
      public void CalcularVlICMS()
      {
          // Instruções para cálculo do valor do ICMS
   
          ...
      }
   
      public void CalcularTotalNotaFiscal()
      {
          // Calcular total da nota considerando
          // descontos e tributos que deverão ser
          // somados ao mesmo
   
          ...
      }
  }
   
  ... 

Nota

Lambda expression é uma espécie de função anônima, ou seja, uma estrutura que conta com um corpo (formado por uma ou mais expressões), mas que não possui um nome e uma organização similar, àquela normalmente presente em métodos comuns. Trata-se de um recurso bastante utilizado como meio para a passagem de parâmetros a outras operações, sem que seja necessária a escrita de construções num formato convencional. Geralmente uma lambda expression é formada por uma instrução curta e, em muitos casos, não excedendo uma linha de código, fato que representa uma interessante vantagem ao evitar que numerosas funções simples precisem ser implementadas ao longo de uma classe.

Listagem 2. Exemplo de uso de uma classe sem o conceito de interface fluente

  ...
   
  NotaFiscal notaFiscal = new NotaFiscal();
  notaFiscal.CalcularVlTotalProdutos();
  notaFiscal.CalcularVlBaseIPI();
  notaFiscal.CalcularVlIPI();
  notaFiscal.AplicarDesconto(vlPercDesconto);
  notaFiscal.CalcularVlBaseICMS();
  notaFiscal.CalcularVlICMS();
  notaFiscal.CalcularTotalNotaFiscal();
   
   
  ... 

O equivalente ao uso do tipo NotaFiscal seguindo um padrão fluente é demonstrado na Listagem 3. Os diversos métodos da classe em questão estão “encadeados” em tal instrução, já que foram implementados em NotaFiscal de um modo que os dispensa da obrigação de referenciar repetidamente a mesma instância. Essa é uma forma mais elegante e, portanto, enxuta de reescrever o código que consta na Listagem 2.

Listagem 3. Exemplo de uso de uma classe baseando-se no conceito de interface fluente

  ...
   
  NotaFiscal notaFiscal = new NotaFiscal()
      .CalcularVlTotalProdutos()
      .CalcularVlBaseIPI()
      .CalcularVlIPI()
      .AplicarDesconto(vlPercDesconto)
      .CalcularVlBaseICMS()
      .CalcularVlICMS()
      .CalcularTotalNotaFiscal();
   
  ... 

A criação de classes dotadas de uma interface fluente acontece tomando-se por base as seguintes diretrizes e recomendações:

• Métodos que possibilitem o encadeamento de instruções devem retornar, obrigatoriamente, uma instância da própria classe na qual foram definidos;

• A instância devolvida como resultado da execução de um método, corresponde exatamente à referência que serviu de base para o seu acionamento. Logo, diz-se que o objeto está se auto-referenciando quando da ocorrência de construções que levem ao encadeamento de operações;

• Serão métodos sem um retorno específico (void) que se prestarão à aplicação de técnicas de programação fluente. Isto não significa que o tipo considerado não contará com outras operações que devolvam valores primitivos ou referências de objetos;

• Uma boa prática de implementação, consiste na separação dos métodos envolvidos na definição de um contexto fluente em uma interface. A partir disso se impede, em grande parte dos casos, que outras estruturas que dependam de uma classe fluente sofram efeitos colaterais quando da realização de alterações na mesma, além de não dificultar a inclusão de novos comportamentos à lógica pré-existente;

• Classes empregadas em diversos pontos de um sistema (e que normalmente correspondem a funcionalidades mais específicas como acesso a dados, validação etc.) representam candidatas potenciais para a utilização do padrão fluente. Tipos com um baixo potencial de reuso dentro de uma aplicação, não justificam os esforços para implementar a técnica aqui descrita.

A Listagem 4 mostra um exemplo simples de definição de um tipo fluente. Os métodos constantes em INotaFiscal devolvem como resultado de sua execução referências baseadas nesta própria interface (ao invés de void como havia sido demonstrado anteriormente). A classe NotaFiscal deriva de INotaFiscal, sendo que os diversos métodos que implementam aquilo que se especificou na interface retornarão a instância em uso naquele momento (através da palavra-chave this).

Listagem 4. Exemplo de uso do conceito de interface fluente

  ...
   
  public interface INotaFiscal
  {
      INotaFiscal CalcularVlTotalProdutos();
      INotaFiscal CalcularVlBaseIPI();
      INotaFiscal CalcularVlIPI();
      INotaFiscal AplicarDesconto(decimal percentual);
      INotaFiscal CalcularVlBaseICMS();
      INotaFiscal CalcularVlICMS();
      INotaFiscal CalcularTotalNotaFiscal();
  }
   
  ...
   
  public class NotaFiscal : INotaFiscal
  {
      public INotaFiscal CalcularVlTotalProdutos()
      {
          ...
   
          return this;
      }
   
      public INotaFiscal CalcularVlBaseIPI()
      {
          ...
   
          return this;
      }
   
      public INotaFiscal CalcularVlIPI()
      {
          ...
   
          return this;
      }
   
      public INotaFiscal AplicarDesconto(decimal percentual)
      {
          ...
   
          return this;
      }
   
      public INotaFiscal CalcularVlBaseICMS()
      {
          ...
   
          return this;
      }
   
      public INotaFiscal CalcularVlICMS()
      {
          ...
   
          return this;
      }
   
      public INotaFiscal CalcularTotalNotaFiscal()
      {
          ...
   
          return this;
      }
  }
   
  ...  ... 

Quer ler esse conteúdo completo? Tenha acesso completo