A Fluent Interface é um padrão de design que permite encadear métodos para criar um código mais legível, parecido com uma linguagem natural. Esse padrão é particularmente útil em cenários complexos, como o de configuração e emissão de documentos fiscais, onde diversos parâmetros precisam ser definidos antes de uma ação ser executada.
Neste artigo, exploraremos como implementar esse padrão em Delphi, aplicando-o a um caso real: a emissão de uma Nota Fiscal Eletrônica (NF-e). A implementação demonstrará como utilizar métodos fluentes para configurar dados obrigatórios, como emitente, destinatário e itens da nota, finalizando com a validação e geração da NF-e.
Implementação de Fluent Interface para NF-e
Vamos imaginar um cenário de emissão de uma NF-e, onde precisamos configurar diferentes aspectos: emitente, destinatário, itens e tributos. Abaixo está a implementação com métodos fluentes.
A Classe Principal TNFeBuilder
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
type TNFeBuilder = class private FEmitente: string; FDestinatario: string; FItens: TStringList; FTotal: Currency; public constructor Create; destructor Destroy; override; function SetEmitente(const AEmitente: string): TNFeBuilder; function SetDestinatario(const ADestinatario: string): TNFeBuilder; function AddItem(const ADescricao: string; AQuantidade: Integer; AValorUnitario: Currency): TNFeBuilder; function CalcularTotal: TNFeBuilder; function GerarXML: string; end; implementation constructor TNFeBuilder.Create; begin FItens := TStringList.Create; FTotal := 0.0; end; destructor TNFeBuilder.Destroy; begin FItens.Free; inherited; end; function TNFeBuilder.SetEmitente(const AEmitente: string): TNFeBuilder; begin FEmitente := AEmitente; Result := Self; end; function TNFeBuilder.SetDestinatario(const ADestinatario: string): TNFeBuilder; begin FDestinatario := ADestinatario; Result := Self; end; function TNFeBuilder.AddItem(const ADescricao: string; AQuantidade: Integer; AValorUnitario: Currency): TNFeBuilder; begin FItens.Add(Format('%s | Qtd: %d | Unit: %.2f', [ADescricao, AQuantidade, AValorUnitario])); FTotal := FTotal + (AQuantidade * AValorUnitario); Result := Self; end; function TNFeBuilder.CalcularTotal: TNFeBuilder; begin // Aqui podem ser aplicados cálculos de impostos ou descontos Result := Self; end; function TNFeBuilder.GerarXML: string; begin Result := Format( '<NFe><Emitente>%s</Emitente><Destinatario>%s</Destinatario><Itens>%s</Itens><Total>%.2f</Total></NFe>', [FEmitente, FDestinatario, FItens.Text, FTotal] ); end; |
Exemplo de Uso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var NFe: TNFeBuilder; XML: string; begin NFe := TNFeBuilder.Create; try XML := NFe.SetEmitente('Empresa ABC LTDA') .SetDestinatario('João da Silva') .AddItem('Notebook', 1, 3500.00) .AddItem('Mouse', 2, 120.00) .CalcularTotal .GerarXML; Writeln(XML); finally NFe.Free; end; end; |
Saída do XML Gerado
1 2 3 4 5 6 7 8 9 10 11 |
<NFe> <Emitente>Empresa ABC LTDA</Emitente> <Destinatario>João da Silva</Destinatario> <Itens> Notebook | Qtd: 1 | Unit: 3500.00 Mouse | Qtd: 2 | Unit: 120.00 </Itens> <Total>3740.00</Total> </NFe> |
Explicando o Código: Conceitos Aplicados
O padrão Fluent Interface utiliza uma técnica simples, porém poderosa, para possibilitar o encadeamento de métodos: retornar a própria instância da classe (Self
). Isso permite que cada método execute sua operação e, ao mesmo tempo, devolva o controle ao objeto que iniciou a sequência. Aqui estão os principais conceitos aplicados no código:
- O Uso de
Self
para Encadeamento
No código, cada método que modifica ou configura a classe termina com a seguinte linha:
1 2 3 |
Result := Self; |
O Self
é uma referência para a própria instância do objeto que está executando o método. Quando o método retorna essa instância, é possível continuar chamando outros métodos sem sair do contexto do objeto. Isso cria o efeito de “fluidez” que caracteriza o padrão Fluent Interface.
Exemplo prático:
No método SetEmitente
, configuramos o emitente e retornamos a instância:
1 2 3 4 5 6 7 |
function TNFeBuilder.SetEmitente(const AEmitente: string): TNFeBuilder; begin FEmitente := AEmitente; // Configura o emitente Result := Self; // Retorna a própria instância end; |
Ao encadear chamadas, como em:
1 2 3 4 |
NFe.SetEmitente('Empresa ABC LTDA') .SetDestinatario('João da Silva'); |
2. Manutenção do Estado Interno do Objeto
Cada método modifica o estado interno do objeto (TNFeBuilder
), atualizando os atributos privados. Isso assegura que os dados sejam acumulados à medida que os métodos são encadeados.
Exemplo:
O método AddItem
adiciona um item à lista interna e recalcula o total:
1 2 3 4 5 6 7 8 |
function TNFeBuilder.AddItem(const ADescricao: string; AQuantidade: Integer; AValorUnitario: Currency): TNFeBuilder; begin FItens.Add(Format('%s | Qtd: %d | Unit: %.2f', [ADescricao, AQuantidade, AValorUnitario])); FTotal := FTotal + (AQuantidade * AValorUnitario); Result := Self; end; |
Ao encadear múltiplos itens:
1 2 3 4 |
NFe.AddItem('Notebook', 1, 3500.00) .AddItem('Mouse', 2, 120.00); |
Os itens são adicionados à lista interna FItens
, e o total é atualizado após cada adição.
3. Retorno Diferente no Último Método
O último método no encadeamento (GerarXML
) não retorna a instância da classe. Em vez disso, ele retorna um resultado final (uma string com o XML). Essa abordagem permite finalizar o fluxo com o dado esperado, sem interromper o padrão fluente.
Exemplo:
1 2 3 4 5 6 7 8 9 |
function TNFeBuilder.GerarXML: string; begin Result := Format( '<NFe><Emitente>%s</Emitente><Destinatario>%s</Destinatario><Itens>%s</Itens><Total>%.2f</Total></NFe>', [FEmitente, FDestinatario, FItens.Text, FTotal] ); end; |
Essa distinção garante que, enquanto os métodos de configuração e processamento retornam a instância para continuar o encadeamento, o método final entrega o resultado da operação.
4. Vantagens do Fluent Interface
- Legibilidade: O código resultante é claro, com métodos que se comportam como frases descritivas.
- Manutenção: As configurações são aplicadas no mesmo objeto, centralizando o estado e reduzindo variáveis temporárias.
- Encapsulamento: Todos os detalhes de implementação são ocultos. O desenvolvedor interage apenas com os métodos necessários.
Comparação:
Sem Fluent Interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var NFe: TNFeBuilder; begin NFe := TNFeBuilder.Create; NFe.SetEmitente('Empresa ABC LTDA'); NFe.SetDestinatario('João da Silva'); NFe.AddItem('Notebook', 1, 3500.00); NFe.AddItem('Mouse', 2, 120.00); NFe.CalcularTotal; Writeln(NFe.GerarXML); end; |
Com Fluent Interface:
1 2 3 4 5 6 7 8 9 10 11 |
Writeln( TNFeBuilder.Create .SetEmitente('Empresa ABC LTDA') .SetDestinatario('João da Silva') .AddItem('Notebook', 1, 3500.00) .AddItem('Mouse', 2, 120.00) .CalcularTotal .GerarXML ); |
Encadeamento Avançado com Retorno à Classe Pai
Vamos avançar um pouco mais? E se quisermos ter outras classes para preenchimento?
O conceito de encadeamento avançado com retorno à classe pai resolve um problema recorrente em APIs fluentes: a necessidade de navegar entre configurações de componentes subordinados sem perder o contexto do objeto principal. Esse modelo é especialmente útil em cenários onde partes específicas de uma configuração (como itens em uma nota fiscal) têm regras ou estruturas próprias, mas ainda precisam fazer parte de um contexto maior, representado pela classe pai.
Motivo para Fazer Isso
A abordagem de retornar à classe pai é necessária quando:
- Modularidade: Partes do sistema, como itens, tributos ou pagamentos, possuem lógica própria e precisam ser organizadas em classes separadas para melhorar a coesão. Isso evita que a classe principal fique sobrecarregada com responsabilidades que poderiam ser delegadas.
- Encadeamento Natural: Para que a API fluente seja contínua e legível, os métodos de uma classe subordinada (como
AddItem
) precisam retornar ao fluxo principal ao final de suas operações. O uso de um método como&End
permite retornar à classe pai sem quebrar o padrão fluente. - Reuso de Componentes: Com componentes subordinados encapsulados em classes específicas (como
TItens
), é possível reutilizá-los em diferentes contextos, sem necessidade de replicar lógica na classe principal. - Hierarquia de Configuração: No exemplo de emissão de NF-e, as configurações possuem uma hierarquia natural: o cabeçalho pertence ao documento principal, enquanto os itens e seus detalhes pertencem a uma parte específica dessa estrutura. Essa hierarquia deve ser refletida no design das classes.
Vantagens do Retorno à Classe Pai
- Legibilidade e Fluidez: O código gerado é mais próximo de uma linguagem natural. Desenvolvedores podem configurar diferentes partes do objeto sem perder o encadeamento ou criar blocos confusos.Exemplo:
1 2 3 4 5 6 7 8 |
NFe .Itens .AddItem('001', 3500.00, 1) .AddItem('002', 120.00, 2) .&End .GerarXML; |
Esse fluxo é muito mais legível do que manter todas as configurações em uma única classe.
Encapsulamento: Cada componente, como TItens
, gerencia seu próprio estado e lógica, sem expor detalhes à classe principal. Isso melhora o design orientado a objetos, facilitando a manutenção e extensão do código.
Organização Modular: Com as classes subordinadas, a lógica específica fica isolada. Por exemplo, a lógica para adicionar itens à nota fiscal está completamente encapsulada em TItens
. A classe principal (TNFeBuilder
) foca em tarefas de alto nível, como gerar o XML.
Extensibilidade: Esse design facilita a adição de novos componentes ou funcionalidades. Caso seja necessário adicionar tributos ou pagamentos à NF-e, bastaria criar novas classes subordinadas e vinculá-las ao fluxo principal.
Menor Acoplamento: A classe subordinada (TItens
) mantém uma referência à classe pai, mas o oposto não é verdadeiro. Isso reduz o acoplamento e melhora a flexibilidade do código.
Melhoria de Testabilidade: Componentes encapsulados podem ser testados isoladamente. Por exemplo, a funcionalidade de adicionar itens e calcular o total pode ser testada diretamente em TItens
, sem interferir na lógica da classe principal.
Resumo
O encadeamento avançado com retorno à classe pai não é apenas uma questão de melhorar a legibilidade. Ele reflete boas práticas de design orientado a objetos, dividindo responsabilidades, reduzindo o acoplamento e tornando o código mais modular e testável. Em sistemas complexos, essa abordagem oferece uma combinação poderosa de flexibilidade, clareza e escalabilidade, essencial para aplicações robustas e fáceis de manter.
Vamos a um exemplo prático:
1. Classe TItens
para Gerenciar os Itens
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
type TNFeBuilder = class; // Declaração forward da classe pai TItens = class private FParent: TNFeBuilder; // Referência à classe pai FItens: TStringList; public constructor Create(AParent: TNFeBuilder); destructor Destroy; override; function AddItem(const AID: string; APreco: Currency; AQuantidade: Integer): TItens; function &End: TNFeBuilder; // Retorna à classe pai end; |
2. Classe TNFeBuilder
para a NF-e
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
type TNFeBuilder = class private FEmitente: string; FCNPJ: string; FEndereco: string; FItens: TItens; // Instância de `TItens` public constructor Create; destructor Destroy; override; // Métodos fluentes para o cabeçalho function SetEmitente(const ARazaoSocial: string): TNFeBuilder; function SetCNPJ(const ACNPJ: string): TNFeBuilder; function SetEndereco(const AEndereco: string): TNFeBuilder; // Método fluente para acessar os itens function Itens: TItens; // Finaliza e gera o XML function GerarXML: string; end; |
3. Implementação dos Métodos
Métodos da Classe TItens
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
constructor TItens.Create(AParent: TNFeBuilder); begin FParent := AParent; // Armazena a referência à classe pai FItens := TStringList.Create; end; destructor TItens.Destroy; begin FItens.Free; inherited; end; function TItens.AddItem(const AID: string; APreco: Currency; AQuantidade: Integer): TItens; begin FItens.Add(Format('ID: %s | Preço: %.2f | Quantidade: %d', [AID, APreco, AQuantidade])); Result := Self; // Permite continuar adicionando itens end; function TItens.&End: TNFeBuilder; begin Result := FParent; // Retorna à classe pai para continuar o encadeamento end; |
4. Exemplo de Uso
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var NFe: TNFeBuilder; XML: string; begin NFe := TNFeBuilder.Create; try XML := NFe .SetEmitente('Empresa ABC LTDA') .SetCNPJ('12.345.678/0001-99') .SetEndereco('Rua das Flores, 123') .Itens .AddItem('001', 3500.00, 1) .AddItem('002', 120.00, 2) .&End .GerarXML; Writeln(XML); finally NFe.Free; end; end; |
Saída do XML Gerado
1 2 3 4 5 6 7 8 9 10 11 |
<NFe> <Emitente>Empresa ABC LTDA</Emitente> <CNPJ>12.345.678/0001-99</CNPJ> <Endereco>Rua das Flores, 123</Endereco> <Itens> ID: 001 | Preço: 3500.00 | Quantidade: 1 ID: 002 | Preço: 120.00 | Quantidade: 2 </Itens> </NFe> |
Explicando o Funcionamento
- Retorno à Classe Pai (
&End
):
A função&End
da classeTItens
retorna a instância da classeTNFeBuilder
, armazenada emFParent
. Isso permite continuar o encadeamento no objeto principal após configurar os itens. - Referência Cruzada:
A classeTItens
conhece sua classe pai (TNFeBuilder
), mas o oposto não ocorre diretamente. Isso garante encapsulamento, permitindo queTItens
administre apenas os dados relacionados aos itens. - Legibilidade:
Esse modelo de encadeamento dentro de encadeamentos melhora a organização e torna o código mais expressivo e próximo da linguagem natural.
Participe da Comunidade no Telegram
🚀 Quer continuar essa discussão e trocar ideias com outros desenvolvedores? Junte-se à nossa comunidade no Telegram! Lá, você pode comentar sobre o que achou deste artigo, tirar suas dúvidas e compartilhar suas experiências com Delphi e ainda discutir ou tirar suas dúvidas sobre os mais variados temas em uma comunidade com mais de 1.000 desenvolvedores.
🔗 Clique aqui para entrar na comunidade
Te vejo lá!
Conclusão
O padrão Fluent Interface, combinado com o uso de Self
, é uma ferramenta poderosa para criar APIs legíveis e práticas. No exemplo de emissão de NF-e, os métodos encadeados simplificam a configuração e emissão, proporcionando um fluxo natural. Ao dominar esse padrão, você melhora significativamente a qualidade e a clareza do código, beneficiando todo o ciclo de desenvolvimento.
Gosto de utilizar esse padrão, nem sabia que tinha um nome. Muito bom artigo.
Obrigado, vamos juntos.