Fluent Interface no Delphi

Fluent Interface no Delphi

Boa noite, pessoal!
Sejam bem-vindos à “quinta temporada” de artigos! Dessa vez não fiz a pausa, já que fiquei ausente um bom tempo há alguns meses.
Antes de retornar aos assuntos envolvendo Engenharia de Software, decidi apresentar uma série de pequenos artigos sobre Delphi. Neste primeiro artigo, o assunto é Fluent Interface!

 

Já ouviu falar em Fluent Interface? Trata-se de um padrão de desenvolvimento de software que permite o encadeamento de métodos para facilitar a escrita e leitura do código. Em outras palavras, é uma técnica que possibilita chamadas seriais dentro do escopo de um mesmo objeto. Um dos propósitos deste padrão é aproximar o código-fonte de uma linguagem natural, como se estivéssemos codificando uma “frase”.

 

Exemplo 1

Sem delongas, apresentarei um exemplo pequeno como introdução deste assunto.
Suponha que seja necessário importar alguns dados, como nomes de cidades, que estejam em um formato fora do padrão semelhante ao texto abaixo:

" Rio+de+janeiro "

Durante essa importação, deseja-se aplicar três regras:

  • Remover os espaços em branco no início e no final do texto;
  • Converter as letras para maiúsculas;
  • Substituir um caracter (“+” por espaço).

Como você escreveria este código? Com Trim, UpperCase e StringReplace, certo?

var
  Cidade: string;
begin
  Cidade := ' Rio+de+janeiro ';
  Cidade := Trim(Cidade);
  Cidade := UpperCase(Cidade);
  Cidade := StringReplace(Cidade, '+', ' ', [rfReplaceAll]);
end;

Embora o código acima seja válido, há uma maneira de melhorá-lo. Nas versões mais recentes do Delphi, é possível usar Fluent Interface para “encadear” estes três métodos:

var
  Cidade: string;
begin
  Cidade := ' Rio+de+janeiro ';
  Cidade := Cidade.Trim.ToUpper.Replace('+', ' ', [rfReplaceAll]);
end;

Bem melhor, não? Todo o código fica em apenas uma linha sem comprometer a leitura. Na verdade, pode-se dizer que o código tornou-se mais legível, não acham?

 

Exemplo 2

Dessa vez, imagine que seja preciso copiar as 10 primeiras letras de um texto e verificar se existe a palavra “INFO”. Vejam só:

var
  Texto: string;
begin
  Texto := 'StatusInfoJunho2018LogProcessamento';
 
  if Copy(Texto, 1, 10).ToUpper.Contains('INFO') then
    ShowMessage('Existe a palavra INFO!');
end;

Fácil, hein? 🙂

 

Fluent Interface na RTL

Agora que vocês já conhecem o Fluent Interface, certamente encontrará algumas aplicações na própria RTL do Delphi. A classe TStringBuilder é um exemplo clássico:

var
  StringBuilder: TStringBuilder;
begin
  StringBuilder := TStringBuilder.Create;
 
  StringBuilder
    .Append('Atalhos do Sistema:')
    .AppendLine.AppendLine
    .Append('F1 - Ajuda').AppendLine
    .Append('F4 - Relatórios').AppendLine
    .Append('F10 - Opções');
 
  ShowMessage(StringBuilder.ToString);
 
  StringBuilder.Free;
end;

O código acima produz o seguinte resultado:

Fluent Interface no Delphi

 

Desenvolvendo classes com Fluent Interface

Ao invés de apenas consumir este recurso, também podemos criá-lo em nossas classes. Para isso, basta que as suas funções retornem o próprio objeto. Na classe de exemplo abaixo, responsável por armazenar colunas de uma instrução SQL, foi aplicado o Fluent Interface no método AddColumn:

type
  TSQLBuilder = class
  private
    FColumns: TStringList;
  public
    function AddColumn(const aColumn: string): TSQLBuilder;
  end;
 
{ TSQLBuilder }
 
function TSQLBuilder.AddColumn(const aColumn: string): TSQLBuilder;
begin
  FColumns.Add(aColumn);
  result := Self; // retorna a própria instância
end;

Como a função retorna o próprio objeto (Self), podemos utilizar a classe dessa forma:

var
  SQLBuilder: TSQLBuilder;
begin
  SQLBuilder := TSQLBuilder.Create;
 
  SQLBuilder
    .AddColumn('Codigo')
    .AddColumn('Nome')
    .AddColumn('Cidade')
    .AddColumn('UF');
 
  { ... }
end;

 

Espero que tirem bom proveito deste recurso, pessoal.
Grande abraço e até logo!


 

13 comentários

  1. Boa noite, André,

    Recurso muito interessando e útil, e por coincidência, hoje tive que usar o “Trim” e “UpperCase” para um tratamento… graças a você, e por mais um ensinamento, é mais uma melhoria que irei aplicar no meu sistema.
    Parabéns e grande abraço.

    1. Boa noite, Daniel! Sempre acompanhando o blog! 🙂
      É isso aí. Aproveite bastante esses recursos. Já já tem mais!
      Obrigado! Abração!

  2. Grande André!

    Mais um excelente post neste blog que já é referência entre os desenvolvedores Delphi.

    Só uma pena que este recurso esteja presente somente nas versões mais novas, pois aqui na empresa eu utilizo o Delphi 2010.

    Continue com seu ótimo trabalho.

    Abraço.

    1. Olá, Cleo!
      Muito obrigado pelo comentário!
      Seria muito bom se este recurso estivesse disponível em versões mais inferiores do Delphi. Por outro lado, essa novidade pode trazer as empresas para as versões mais recentes! 🙂

      Grande abraço!

  3. Só é uma pena não podermos acrescentar mais de uma unit contendo helpers na cláusula USES. Infelizmente pelo que vi, somente a última declaração é que vale. Será que existe uma forma de contornar isso sem ter que colocar todos os helpers na mesma unit?

    1. Boa tarde, Eduardo, tudo bem?
      Desconheço essa restrição. Utilizo a versão Tokyo e consigo referenciar e usar duas ou mais units com helpers na cláusula uses. Talvez essa restrição exista na versão que você está utilizando.
      Abraço!

  4. Boa tarde André
    Uso hoje a versão Seattle, mas vejo a limitação ocorrer à várias versões. Para reproduzir faça o seguinte:
    – Declare no USES a unit System.SysUtils, que contém helpers para o tipo String.
    – Declare esse helper e implemente o método TESTE.
    Type
    TTeste = record helper for String
    public
    function Teste: String;
    end;

    Pronto! Tente usar os helpers que estão contidos na unit System.SysUtils e verá que somente o método Teste do helper declarado está visível. Se o helper estiver em outra unit, experimente brincar com a ordem da declaração no uses entre System.SysUtils e a unit que contém o helper TTeste.

    Abraço!

    1. Tem razão, Eduardo.
      Como apenas uso Helpers para classes (class helpers) e não para tipos (record helpers), eu ainda não havia encontrado essa limitação.
      Infelizmente não há uma forma simples de contorná-la. O tópico abaixo do StackOverflow entra em mais detalhes:

      Is it possible to use two record helpers for the string type?

      Abraço!

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *