
Última atualização em 29 de agosto de 2025 por Willian Tuttoilmondo
Uma das grandes vantages do desenvolvimento orientado a objetos é a possibilidade de criarmos fluent interfaces em Delphi. Assim como em outras linguagens de desenvolvimento que suportam o paradigma, Delphi também permite que recursos avançados sejam incorporados ao desenvolvimento, trazendo concisão, fluidez e, principalmente, facilidade de manutenção. O conceito é simples: até que se prove o contrário, qualquer método em uma interface deve ser uma função que retorna a instância desta mesma interface. Se você não está acostumado com o conceito de desenvolvimento orientado a interfaces, que é parte integrante do paradigma de desenvolvimento orientado a objetos, dê uma olhada aqui.
Neste artigo, vamos explorar o conceito de fluent interfaces em Delphi, com exemplos práticos, revisitando nossos códigos antigos, transformando-os em algo mais simples e natural.
Delphi e a orientação a objetos
O casamento do Delphi com o paradigma de orientação a objetos nasceu bem antes do próprio Delphi. Quando a Borland lançou a biblioteca TurboVision – você pode encontrar um manual do Turbo Pascal 6.0 com Turbo Vision aqui -, a orientação a objetos já estava lá. O Delphi já carrega consigo esse paradigma desde sua concepção e, o mais provável, é que as pessoas ignorem este fato e acreditem que nada do que o paradigma implementa seja possível de se fazer em Delphi. Mas, como sempre, isso é apenas um engano pavoroso e que, por muitas vezes, tem afastado profissionais e empresas desta tecnologia.
Mas, deixando a panfletagem de lado, vamos aos fatos.
Princípios de fluent interfaces em Delphi
Interfaces são estruturas de código que, em suma, definem um componente. Um contrato, se assim preferirem, que diz o que este componente tem a seu dispor, sejam métodos ou propriedades. Contudo, vale lembrar:
- Interfaces não têm construtores ou destrutores;
- Propriedades são sempre públicas e a interação com elas se dará, sempre, por getters e setters;
- Interfaces não admitem variáveis ou constantes e;
- Interfaces não implementam código.
Tendo isso em vista, construir fluent interfaces em Delphi deve obedecer, sempre, estas quatro regras e, além delas, uma quinta: até que se prove o contrário, todos os métodos da interface são funções que retornam sua própria instância. Em algum momento existirá um método que não será uma função ou, mesmo que seja, deva ter um retorno diferente, mas esses são casos “excepcionais” e serão tratados dessa forma de agora em diante.
Quando falamos sobre padrões de projeto em Delphi, vimos que podemos criar classes com estruturas idênticas para usar em uma abstract factory de modo a facilitar sua invocação e, por consequência, sua utilização. Então, partindo desse conceito, vamos transformar nossa “calculadora” em uma estrutura mais dinâmica usando fluent interfaces em Delphi.
Pra começarmos, vamos ver nossa classe de base.
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 | unit Classes.Operacao; interface type TOperacao = class abstract strict private FB: Double; FA: Double; procedure SetA(const Value: Double); procedure SetB(const Value: Double); public constructor Create; virtual; function Efetuar: Double; virtual; abstract; property A: Double read FA write SetA; property B: Double read FB write SetB; end; TOperacaoClass = class of TOperacao; implementation { TOperacao } constructor TOperacao.Create; begin FA := 0; FB := 0; end; procedure TOperacao.SetA(const Value: Double); begin if FA = Value then begin Exit; end; FA := Value; end; procedure TOperacao.SetB(const Value: Double); begin if FB = Value then begin Exit; end; FB := Value; end; end. |
Esse é um modelo clássico de classe, com propriedades e membros públicos e privados, com o qual estamos todos acostumados. Mas, lembrando que construir fluent interfaces em Delphi vai além de construir classes, vamos começar pelo começo: definindo nossa interface.
A implementação de interfaces por meio de classes tem apenas uma regra: todos os membros de uma interface, excetuando as propriedades, devem estar presentes na classe, mesmo que de forma abstrata. Sendo assim, ao redesenharmos nossa classe como uma interface, teremos:
1 2 3 4 5 6 7 8 9 10 | IOperacao = interface ['{BD5131FE-BED6-4E8C-9407-FFE8348F8DC2}'] function GetA: Double; function GetB: Double; function Efetuar(out AResult: Double): IOperacao; function SetA(const AValue: Double): IOperacao; function SetB(const AValue: Double): IOperacao; property A: Double read GetA; property B: Double read GetB; end; |
As diferenças entre as duas abordagens são bem visíveis. Como estamos construindo fluent interfaces em Delphi, precisamos que todos os métodos que serão de visibilidade pública sejam transformados em funções e que retornem a instância da própria interface. A outra alteração está nas propriedades, antes marcadas como leitura/escrita e, agora, como apenas leitura. Com isso, os setters acabaram sendo tranformados em funções que, igualmente, retornam a instância da própria interface.
Assim, a implementação de uma classe baseada nesta interface tomaria esta forma:
1 2 3 4 5 6 7 8 9 10 11 12 13 | TOperacao = class abstract(TInterfacedObject, IOperacao) strict private function GetA: Double; function GetB: Double; strict protected FA: Double; FB: Double; public constructor Create; virtual; function Efetuar(out AResult: Double): IOperacao; virtual; abstract; function SetA(const AValue: Double): IOperacao; virtual; function SetB(const AValue: Double): IOperacao; virtual; end; |
Como já vimos no nosso artigo sobre interfaces, a implementação deve sempre partir de TInterfacedObject ou suas descendentes, implementando todos os membros que estão definidos na interface indicada na sua definição. Como não precisamos implementar as propriedades, pois estas já possuem visibilidade pública, a classe não precisa tê-las em seu contrato. Mas, caso você precise alterar esta visibilidade, elas deverão ser declaradas da mesma forma em que foram declaradas na interface, no escopo de visibilidade escolhido.
Outro detalhe, e este tem a ver com o conceito de SOLID, é que esta classe deve obedecer o conceito aberto/fechado, permitindo que descendentes estendam suas funcionalidades – e é para isso que temos os marcadores dynamic e virtual em Delphi -, evitando que alterações sejam feitas nela mesma. Ao final, teremos esta implementação para a classe base:
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 66 67 68 69 70 71 72 73 74 75 76 77 | unit Devspace.Classes.Operacao; interface type IOperacao = interface ['{BD5131FE-BED6-4E8C-9407-FFE8348F8DC2}'] function GetA: Double; function GetB: Double; function Efetuar(out AResult: Double): IOperacao; function SetA(const AValue: Double): IOperacao; function SetB(const AValue: Double): IOperacao; property A: Double read GetA; property B: Double read GetB; end; TOperacao = class abstract(TInterfacedObject, IOperacao) strict private function GetA: Double; function GetB: Double; strict protected FA: Double; FB: Double; public constructor Create; virtual; function Efetuar(out AResult: Double): IOperacao; virtual; abstract; function SetA(const AValue: Double): IOperacao; virtual; function SetB(const AValue: Double): IOperacao; virtual; end; TOperacaoClass = class of TOperacao; implementation { TOperacao } constructor TOperacao.Create; begin FA := 0; FB := 0; end; function TOperacao.GetA: Double; begin Result := FA; end; function TOperacao.GetB: Double; begin Result := FB; end; function TOperacao.SetA(const AValue: Double): IOperacao; begin Result := Self; if FA = AValue then begin Exit; end; FA := AValue; end; function TOperacao.SetB(const AValue: Double): IOperacao; begin Result := Self; if FB = AValue then begin Exit; end; FB := AValue; end; end. |
Um padrão de projeto aqui, outro ali e voilà!
Como já vimos os conceitos de abstract factories e singletons anteriormente, não vou me estender revendo estes conceitos. Como nosso foco é desenvolver fluent interfaces em Delphi, vamos direto ao ponto. Nossa abstract factory, aqui, também obedecerá este conceito, ficando desta forma:
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | unit Devspace.Classes.Operacao.Factory; interface uses System.Generics.Collections, Devspace.Classes.Operacao; type TOperacoes = TArray<string>; IOperacaoFactory = interface ['{16609C69-1A6D-4365-8203-EB15F8AAF682}'] function GetOperacoes: TOperacoes; function RegistraOperacao(const ANome: string; const AOperacao: TOperacaoClass): IOperacaoFactory; function ObtemOperacao(const ANome: string): IOperacao; property Operacoes: TOperacoes read GetOperacoes; end; TOperacaoFactory = class(TInterfacedObject, IOperacaoFactory) strict private FOperacoes: TObjectDictionary<string, TOperacaoClass>; function GetOperacoes: TOperacoes; public constructor Create; virtual; destructor Destroy; override; class function Instance: IOperacaoFactory; function RegistraOperacao(const ANome: string; const AOperacao: TOperacaoClass): IOperacaoFactory; function ObtemOperacao(const ANome: string): IOperacao; end; implementation { TOperacaoFactory } constructor TOperacaoFactory.Create; begin FOperacoes := TObjectDictionary<string, TOperacaoClass>.Create; end; destructor TOperacaoFactory.Destroy; begin FOperacoes.Free; inherited; end; function TOperacaoFactory.GetOperacoes: TOperacoes; begin Result := FOperacoes.Keys.ToArray; end; function TOperacaoFactory.ObtemOperacao(const ANome: string): IOperacao; begin Result := nil; if not FOperacoes.ContainsKey(ANome) then begin Exit; end; Result := FOperacoes[ANome].Create; end; function TOperacaoFactory.RegistraOperacao(const ANome: string; const AOperacao: TOperacaoClass): IOperacaoFactory; begin Result := Self; FOperacoes.AddOrSetValue(ANome, AOperacao); end; var FInstance: IOperacaoFactory; class function TOperacaoFactory.Instance: IOperacaoFactory; begin if not Assigned(FInstance) then begin FInstance := TOperacaoFactory.Create; end; Result := FInstance; end; initialization FInstance := nil; end. |
Implementando as operações básicas
Com o conceito de fluent interfaces em Delphi bem consolidado e com nossa estrutura de base já criada, vamos implementar as quatro operações básicas da matemática – Adição, Subtração, Multiplicação e Divisão – seguindo o conceito criado por IOperacao.
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | unit Devspace.Classes.Operacao.Basicas; interface uses Devspace.Classes.Operacao; type TAdicao = class(TOperacao) public function Efetuar(out AResult: Double): IOperacao; override; end; TSubtracao = class(TOperacao) public function Efetuar(out AResult: Double): IOperacao; override; end; TMultiplicacao = class(TOperacao) public function Efetuar(out AResult: Double): IOperacao; override; end; TDivisao = class(TOperacao) public function Efetuar(out AResult: Double): IOperacao; override; end; implementation uses System.SysUtils, Devspace.Classes.Operacao.Factory; { TAdicao } function TAdicao.Efetuar(out AResult: Double): IOperacao; begin Result := Self; AResult := FA + FB; end; { TSubtracao } function TSubtracao.Efetuar(out AResult: Double): IOperacao; begin Result := Self; AResult := FA - FB; end; { TMultiplicacao } function TMultiplicacao.Efetuar(out AResult: Double): IOperacao; begin Result := Self; AResult := FA * FB; end; { TDivisao } function TDivisao.Efetuar(out AResult: Double): IOperacao; begin Result := Self; if FB = 0 then begin raise EDivByZero.Create('O valor de B não pode ser 0'); end; AResult := FA / FB; end; initialization TOperacaoFactory.Instance .RegistraOperacao('Adição', TAdicao) .RegistraOperacao('Subtração', TSubtracao) .RegistraOperacao('Multiplicação', TMultiplicacao) .RegistraOperacao('Divisão', TDivisao); end. |
É importante notar que todas as implementações descendentes de TOperacao não precisam definir novas interfaces. Elas apenas estendem a implementação do método Efetuar, aplicando, cada uma, sua operação para os termos A e B – e alertando o usuário de uma possível divisão por zero na classe TDivisao. Notem também que todas as implementações trazem Result := Self; como sua primeira linha, indicando que o retorno do método sempre será a instância da interface. Quando desenvolvemos fluent interfaces em Delphi, essa é a regra essencial.
Mas, e esse é o charme da coisa toda, a primeira razão para usarmos fluent interfaces em Delphi está na seção de inicialização do arquivo. E é sobre ela que falaremos a seguir.
Razão nº 1 para implementar fluent interfaces em Delphi: encadeamento
Como os métodos públicos implementados em uma classe retornam sua própria instância, podemos encadear as chamadas a estes métodos de maneira bem simples. Ao contrário do desenvolvimento tradicional, onde teríamos quatro linhas de código – uma para cada operação -, o compilador considerará esta como apenas uma linha.
A vantagem desta abordagem está na forma como as ferramentas de qualidade de código tratarão as fluent interfaces em Delphi, indicando que nosso código, por ter um número reduzido de linhas, vai se tornando menos tóxico e de manutenção mais simples.
Além disso, quando pensamos nos conceitos do código limpo e suas aplicações, o uso de fluent interfaces em Delphi nos traz o que é considerada a segunda razão para justificar seu uso.
Razão nº 2 para implementar fluent interfaces em Delphi: leitura aprimorada do código
Assim como nosso idioma é fluido, o uso de fluent interfaces em Delphi torna a própria linguagem fluida. Ler uma instrução onde cada operação realizada com um objeto é exposta sem a necessidade de uma nova linha de código e a invocação de sua variável de instância ou, pior ainda, usando uma estrutura with – nada contra seu uso, mas tente debugar algo com isso e sinta uma raiva imensa -, é de saltar aos olhos.
Logicamente isso só faz sentido, em termos de legibilidade, se você adicionar cada método em uma linha, como no exemplo. Caso contrário, isso será apenas uma linha muito extensa.
Com isso, chegamos na terceira razão para usarmos fluent interfaces em Delphi.
Razão nº 3 para implementar fluent interfaces em Delphi: a criação de DSLs
Uma DSL – do Inglês Domain-Specific Language – é uma linguagem de domínio específico, desenvolvida para tornar mais natural a leitura de instruções dentro da resolução de um problema. No nosso dia-a-dia, temos contato com várias delas, como SQL, HTML e CSS, por exemplo.
Quando usamos fluent interfaces em Delphi, criamos uma estrutura que se assemelha muito a uma DSL, pois a sintaxe criada difere do conjunto de instruções que todos conhecemos. Como usamos, por exemplo, TClasseDoObjeto.Instance.SetID(1) ao invés de VariavelDoObjeto := TClasseDoObjeto.Create e VariavelDoObjeto.Id := 1, acabamos por instituir uma nova sintaxe exclusiva para este domínio de informação, no caso, o componente que implementamos usando fluent interfaces em Delphi.
Isso só se dá pelo encadeamento das instruções, que é a nossa primeira razão, e impacta diretamente da legibilidade, nossa segunda razão. O fato é: usar fluent interfaces em Delphi nos garante um código limpo, eficiente e muito bem escrito.
Fluent interfaces em Delphi: uma aplicação prática
Usando fluent interfaces em Delphi, podemos transformar aquela nossa calculadora em um projeto com um código mais simples e funcional. Vocês vão notar que a legibilidade do código gerado é limpa, simples e fluida, como todo bom código deve ser.
Para começar, vamos desenhar o seguinte formulário:

Não se preocupem com as propriedades dos componentes. Como sempre, o código fonte estará disponível para download no final do artigo.
Mesmo assim, este é o código fonte do formulário:
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 66 67 68 69 70 71 72 73 74 75 | unit Devspace.Forms.Calculadora; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Vcl.Mask; type TCalculadora = class(TForm) edtA: TLabeledEdit; edtB: TLabeledEdit; rgOperacao: TRadioGroup; btnCalcular: TButton; lblResultadoDesc: TLabel; lblResultado: TLabel; procedure FormCreate(Sender: TObject); procedure btnCalcularClick(Sender: TObject); end; var Calculadora: TCalculadora; implementation uses Devspace.Classes.Operacao.Factory; {$R *.dfm} procedure TCalculadora.btnCalcularClick(Sender: TObject); var Resultado: Double; begin if edtA.Text = EmptyStr then begin edtA.Text := '0'; end; if edtB.Text = EmptyStr then begin edtB.Text := '1'; end; TOperacaoFactory.Instance .ObtemOperacao(rgOperacao.Items[rgOperacao.ItemIndex]) .SetA(StrToFloat(edtA.Text)) .SetB(StrToFloat(edtB.Text)) .Efetuar(Resultado); lblResultado.Caption := FloatToStr(Resultado); end; procedure TCalculadora.FormCreate(Sender: TObject); var Operacao: string; begin for Operacao in TOperacaoFactory.Instance.Operacoes do begin rgOperacao.Items.Add(Operacao); end; rgOperacao.ItemIndex := 0; end; end. |
Deem uma olhada no evento TCalculadora.btnCalcularClick, mais precisamente na linha 55. Ali vocês conseguem perceber o poder da implementação de fluent interfaces em Delphi. Com ela, foi possível interagir não com um, mas com dois objetos diferentes na mesma linha de instrução.
Ao invocar o singleton da abstract factory, foi possível chamar o método ObtemOperacao, o qual retorna um objeto derivado da interface IOperacao. A partir dele, foi possível invocar os métodos SetA e SetB, que atribuem os valores aos termos da operação e, em seguida, invocar o método Efetuar, o qual realizará a operação escolhida.
E tudo isso em apenas uma linha!
Fantástico, não é mesmo?
Ao realizarmos a análise da toxicidade do código, vemos que o evento TCalculadora.btnCalcularClick é o mais extenso, com 8 linhas, como mostrado abaixo.

Se não utilizássemos os conceitos de fluent interfaces em Delphi, este número seria de, pelo menos, 13 linhas. Num código simples como este, esse parece um ganho pequeno, mas se pensarmos em um software grande, as vantagens começam a fazer mais sentido.
Em operação, nossa calculadora tem este comportamento:

Ampliando as operações matemáticas
Depois de vermos como a implementação de fluent interfaces em Delphi pode trazer várias vantagens para o desenvolvimento, vamos nos divertir e ampliar nossa calculadora com mais duas operações: potenciação e radiciação.
Para tal, vamos criar um novo arquivo, como o que está abaixo:
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 | unit Devspace.Classes.Operacao.Potencia; interface uses Devspace.Classes.Operacao; type TPotencia = class(TOperacao) public function Efetuar(out AResult: Double): IOperacao; override; end; TRaiz = class(TOperacao) public function Efetuar(out AResult: Double): IOperacao; override; end; implementation uses System.Math, System.SysUtils, Devspace.Classes.Operacao.Factory; { TPotencia } function TPotencia.Efetuar(out AResult: Double): IOperacao; begin Result := Self; AResult := Power(FA, FB); end; { TRaiz } function TRaiz.Efetuar(out AResult: Double): IOperacao; begin Result := Self; if FB = 0 then begin raise EDivByZero.Create('O valor de B não pode ser 0'); end; AResult := Power(FA, 1/FB); end; initialization TOperacaoFactory.Instance .RegistraOperacao('Potência', TPotencia) .RegistraOperacao('Raiz', TRaiz); end. |
E, sem alterar mais nenhuma linha de código na aplicação, teremos este resultado ao recompilarmos nossa calculadora:

Legal, né?! Mas essa mágica só é possível quando as units que usam a seção initialization estão adicionadas ao projeto.
Em suma…
Implementar fluent interfaces em Delphi é simples e rápido. Isso tornará seu código mais limpo, legível e muito mais fácil de compreender. Mas, como gosto sempre de colocar em todos os artigos, usar fluent interfaces em Delphi vai depender, e muito, da maturidade dos profissionais envolvidos. É necessário entender que, antes de tudo, essa abordagem modifica a forma como enxergamos a linguagem de programação, o que pode ser um dificultador para desenvolvedores que estão muito presos ao modelo RAD.
Desenvolver utilizando fluent interfaces em Delphi não é o futuro. É o presente.
E como sempre, aqui vocês encontrarão o código fonte produzido neste artigo. Só um aviso: este código é compatível apenas com as versões mais recentes do IDE. Até o fechamento deste artigo, a versão oficial é a 12.3 Athens, a mesma utilizada na produção do código.
Até a próxima!
Faça um comentário