Dominando o Callback Pattern em Delphi: Código limpo, fluido e sem memory leaks

Callback Pattern em Delphi - DevSpace
Callback Pattern em Delphi - DevSpace

Última atualização em 20 de maio de 2026 por Willian Tuttoilmondo

Você já olhou para aquele emaranhado de blocos try..finally, variáveis globais e heranças complexas no seu projeto e pensou: “Sério que a gente ainda codifica assim em Object Pascal?” Se a sua resposta foi sim, você está prestes a virar o jogo. No desenvolvimento moderno, a busca por legibilidade e performance nos levou a resgatar e refinar conceitos poderosos. Hoje, vamos falar sobre como implementar o Callback Pattern em Delphi integrado a uma Fluent Interface (Interface Fluida) com gerenciamento automático de memória via ARC (Contagem de Referências).

Imagine disparar uma rotina complexa de envio de e-mails, configurar parâmetros como se estivesse escrevendo uma frase e gerenciar o sucesso ou a falha do processo na mesma linha, usando funções anônimas. É exatamente essa elegância sintática e eficiência que a arquitetura moderna nos proporciona. Neste artigo, você vai aprender a estruturar do zero uma DSL (Domain-Specific Language) em Delphi, entender o ciclo de vida de objetos baseados em escopo e dominar o uso de métodos assíncronos e callbacks para transformar seu código legado em uma obra de arte da engenharia de software.

O que é a arquitetura Callback Pattern

Nascida para simplificar o consumo de componentes de alta complexidade, a arquitetura baseada em Callback Pattern traz consigo uma marca muito difícil de ser superada: a legibilidade. Enquanto o desenvolvimento tradicional em Delphi foca em blocos try..except e try..finally para marcar os tratamentos de exceção e garantir que objetos criados sejam efetivamente destruídos, demandando linhas e linhas de código, controle estrito dos marcadores e, com isso, um código de leitura difícil e extensa, usar o Callback Pattern faz com que praticamente toda a sua rotina se resolva em uma única linha.

Mesmo que, por trás do seu componente, as mesmas estruturas ainda sejam utilizadas, o componente final, o qual será consumido pelo desenvolvedor na aplicação ao desenvolver a regra de negócio, será muito mais simples e direto, demandando poucas linhas para resolver um problema. Assim, o código que passará por manutenções mais frequentes estará sempre limpo, simples e de fácil leitura.

Como implementar o Callback Pattern em Delphi

Ao contrário do que muitos podem pensar, a linguagem Delphi implementa infinitos recursos que podem ser utilizados em conjunto para a obtenção de um código moderno e limpo. Um desses recursos já vimos antes, que são as interfaces fluidas – ou fluent interfaces. Isso sempre esteve ali, com o Delphi, desde o início, mas pouquíssimos desenvolvedores sabiam o que estavam fazendo e, na imensa maioria dos casos, não usar este recurso acabava por deixar o código, que hoje chamamos de legado, sujo e com manutenção muito difícil.

Outro recurso já tínhamos também, desde o começo, eram os callbacks. Mesmo que de uma forma diferente, event notifiers declarados como procedure of object funcionavam como callbacks. O modelo de callback como conhecemos, onde uma parcela de código procedural pode ser passada como parâmetro em um método, veio até nós com a versão 2009.

Por último, um recurso importantíssimo para implementarmos o callback pattern em Delphi também sempre esteve ali: o ARC. Ele nos permite instanciar objetos apenas pelo tempo necessário, sem que precisemos controlá-lo diretamente.

Quando trabalhamos com interfaces, o compilador utiliza o mecanismo de ARC no Delphi (do inglês, Automatic Reference Counting) para rastrear o ciclo de vida dos objetos de forma totalmente determinística. A cada nova atribuição ou passagem do objeto como parâmetro por meio de uma interface, o Delphi injeta secretamente uma chamada ao método _AddRef, incrementando o contador interno; assim que essa referência sai de escopo – o que acontece imediatamente ao término da execução de uma linha com objetos temporários e anônimos -, o método _Release é acionado de forma automática para decrementar o valor. Quando esse contador finalmente zera, o próprio ecossistema do Object Pascal se encarrega de destruir a instância e liberar a memória instantaneamente, eliminando de vez a necessidade do clássico bloco try…finally e blindando sua aplicação contra os temidos memory leaks.

Sendo assim, com a união destes três “super poderes”, podemos implementar o callback pattern em Delphi tranquilamente.

A base de tudo está na interface

Como já vimos em artigos anteriores, usar interfaces nos traz vantagens imensas, como a delimitação mínima do escopo de uma classe, a segmentação de uma interface com muitas atribuições em interfaces menores e mais concisas e, principalmente, o controle automático da instância pelo ARC. Então, para que qualquer projeto baseado em callback pattern em Delphi comece, precisamos estabelecer as interfaces que servirão de base.

Implementando as bases do projeto

Para começar, precisamos instituir a interface base para as operações, que ficará desta forma:

Já vimos isso antes quando estudamos fluent interfaces para nossos projetos. Além do que já vimos, mais nada precisará ser acrescentado. Portanto, se você se sentir perdido, explore os artigos sobre interfaces aqui no blog para obter mais informações.

Definida a interface, a unit que conterá tanto esta interface quanto sua aplicação, ficará definida desta forma:

Agora, precisaremos instituir uma Abstract Factory e seu Singleton, os quais já vimos no artigo sobre padrões de projeto. A seguir, veremos a definição de sua interface e sua implementação diretamente na unit que as abriga.

Agora, vamos implementar as quatro operações básicas, como de costume.

Um ponto importante a ser notado: ao invés de estendermos as classes modificando o método Calculate, criamos um método protegido chamado DoCalculate para esta extensão. Esta é uma questão de boas práticas, uma vez que, se necessário, podemos implementar mecanismos de validação ou qualquer outro processamento que seja necessário sem precisar repeti-lo nas extensões. Assim, apenas a parte que realmente for diferente entre as classes precisará ser escrita sem qualquer necessidade de reimplementar códigos definidos em outras classes.

Implementando as units auxiliares

unit Devspace.Classes.Operation.Basics.pas usa em sua seção de implementação a unit Devspace.Exceptions.Operation.pas, a qual é definida desta forma:

Ela implementa um class helper, algo que também já vimos anteriormente, para a classe de exceção EDivByZero. Além disso, há a implementação de uma nova classe de exceção, à qual demos o nome de EOnErrorException. Notem que, em ambos os casos, temos um class procedure para usarmos na elevação da exceção. Isso deixa nosso código mais limpo e é mais um dos inúmeros itens a considerar no nosso manual de boas práticas.

E, seguindo nosso manual de boas práticas, esta unit usa a unit Devspace.Consts.Operation.pas para abrigar suas constantes e resource strings. Então, esta unit fica definida desta forma:

Implementando o Callback Pattern em Delphi

Agora que nossa base foi definida e implementada, vamos explorar o desenvolvimento do Callback Pattern em Delphi. Começaremos pela definição dos tipos e interfaces e, no final, colocaremos as definições e implementações na mesma unit.

Definindo o formato dos métodos de callback

A forma mais simples de definir um método de callback é usarmos a estrutura TProc. Nela, as definições de ponteiros para métodos estão todas estabelecidas e isso nos permite escrever qualquer método para ser usado como callback. Contudo, às vezes precisamos de coisas mais específicas, como parâmetros estritamente definidos para que nosso método de callback possa utilizá-lo em seu processamento.

Então, para resolver essa questão, o Delphi implementa um recurso de referência ao ponteiro, o qual pode ser definido usando a expressão reservada reference to procedure. Vale lembrar que, para callbacks, funções não devem ser utilizadas pois não há como capturar seu retorno. Portanto, quando implementarmos o Callback Pattern em Delphi, utilizaremos apenas procedures como callback.

Sendo assim, definiremos os métodos de callback da seguinte maneira:

Assim, nosso callback de erro nos trará a mensagem de erro em seu parâmetro e nosso callback de sucesso nos trará o resultado da operação.

Definindo as interfaces usadas pelo callback pattern

Uma vez definidos os métodos de callback, definiremos as interfaces que irão compor este recurso, começando pela interface IOnSuccess, a qual é responsável por prover a chamada de callback de execução bem sucedida.

E agora, a definição da interface da nossa facade, a qual será consumida na aplicação para a execução da operação em si.

As definições são simples e não implementam nenhuma complexidade. Contudo, é importante notar a separação destas interfaces pois, assim, o componente nos obrigará a tratar o erro na operação antes do sucesso. Isso garante, até certo ponto logicamente, que o erro não seja suprimido quando ele ocorrer.

O Callback Pattern em Delphi acontecendo

Definidos os métodos de callback, a interface com a chamada de callback de operação bem sucedida e da nossa facade, vamos juntar tudo e implementar o Callback Pattern em Delphi.

Notem três pontos muito importantes nesta implementação:

  1. O construtor da classe TOperationFacade está em uma seção privada;
  2. A classe TOperationFacade possui uma class function chamada TOperationFacade.New que retorna uma instância de IOperationFacade; e
  3. O método TOperationFacade.OnError não executará suas operações se seu callback não estiver definido apropriadamente.

A razão pela qual o construtor da classe foi colocado em uma seção privada está intimamente ligada ao método TOperationFacade.New. Como queremos que a instância da facade seja criada com este método e não pelo construtor, devemos ocultá-lo para chamadas externas à unit, mas permitir que ele seja acessado internamente. Para isso, usamos uma caracteŕistica de escopo do Delphi que permite que seções privadas de uma classe sejam acessadas diretamente em qualquer lugar da unit onde se encontram. Assim, externamente, apenas o método TOperationFacade.New permanecerá visível, com ele podendo acessar o construtor da classe diretamente.

Notem, também, que o método TOperationFacade.Calculate invoca a instância da Abstract Factory para obter a operação desejada, passada por parâmetro no método TOperationFacade.New e armazenada na variável FOperation. Isso faz com que nossa facade “encapsule” uma instância baseada em IOperation, permitindo que a operação seja realizada internamente. Ele ainda é responsável pelo tratamento de qualquer exceção que venha a ser elevada, retirando do desenvolvedor a responsabilidade de fazê-lo na camada de visão (formulário), por exemplo.

Atenção: como a facade encapsula e silencia a exceção original para tratá-la via callback, certifique-se de sempre mapear o método OnError no seu código de consumo, evitando que erros críticos passem despercebidos.

Tá, mas, e na prática, como fica?

Como não poderia deixar de ser, vamos gerar uma aplicação prática para exemplificar o consumo de um componente construído com Callback Pattern em Delphi. Ela será a clássica calculadora que implementa operações por meio de um componente TComboBox e exibe o resultado em um componente TLabel.

Sendo assim, a unit do formulário ficará desta forma:

Esta unit usa, em sua implementação, uma unit chamada Devspace.Helpers.TStrings.pas, a qual implementa um class helper para o tipo TStrings – o que pode ser visto no método TCalculator.ComboBoxLoadItems, na instrução OperationComboBox.Items.FromArray(TOperationFactory.Instance.Operations);.

Esta unit é definida da seguinte maneira:

Usando o Callback Pattern em Delphi para simplificar o código

Vamos observar o código abaixo para entendermos melhor como o callback pattern deixa nosso código mais simples e limpo.

Percebam que, para questões de leitura, este código pode ser lido quase como uma linguagem natural. Quando isso acontece, mesmo que a sintaxe da linguagem aponte para outra direção – uma leitura mais “quebrada” -, forma-se uma “nova” linguagem, específica daquele domínio e não da linguagem de programação utilizada. Esta linguagem de domínio específico, ou DSL, permite que, ao invés de múltiplas linhas de codificação, foquemos apenas no necessário, facilitando a compreensão da rotina como um todo.

Sendo assim, ao ler esta parcela de código, naturalmente compreendemos que:

  1. Usaremos um componente TOperationFacade;
  2. O método New gera um novo componente a partir da operação selecionada no componente TComboBox;
  3. Atribuiremos ao valor A aquilo que estiver contido no componente chamado AEdit, convertendo seu valor para Double;
  4. O mesmo acontece para o valor B, por meio do componente chamado BEdit;
  5. O método Calculate efetuará o cálculo;
  6. O método OnError executará o método de callback em caso de exceção durante o cálculo; e
  7. O método OnSuccess executará o método de callback em caso de operação bem sucedida.

Para o compilador, isto representa apenas uma linha de codificação, com cada linha dos métodos de callback sendo contabilizadas separadamente.

Em contrapartida, caso fizésemos uso do modelo “tradicional” de desenvolvimento, nosso código teria esta aparência:

Não é necessariamente feio, mas muito menos elegante que a versão anterior.

Expandindo o projeto com novas operações

Assim como já vimos, qualquer implementação nova que será adicionada à aplicação deve seguir as regras do princípio L do conceito SOLID – o princípio da substituição de Liskov. As classes a serem implementadas devem seguir à risca a implementação definida pela interface. Ou seja, para criarmos novas operações, elas devem derivar de IOperation, sendo implementadas segundo suas diretrizes.

Aqui está um exemplo de como adicionaríamos as operações de potência e radiciação à calculadora.

Tudo isso é lindo, mas posso usar nos meus projetos legados?

Eu sempre digo que o uso de recursos avançados de desenvolvimento depende sempre da maturidade da equipe em assimilar e saber como utilizar estes recursos. Eles não são recursos novos, nem complexos demais para não serem usados e acredito que, com um bom planejamento, aplicações legadas podem ser refatoradas sem muito esforço para modelos mais avançados, como este. Cabe a cada um decidir como e quando. Mas, uma coisa é certa: depois que você começa, não para mais.

E pra fechar…

Espero que todos tenham aproveitado todo o caminho até aqui. Para chegar a este artigo, um longo caminho, tratando de orientação a objetos, padrões de projeto, interfaces e fluent interfaces foi trilhado, mostrando que programar bem requer estudo e construção gradual do conhecimento.

E, como já é tradição por aqui, o código fonte deste artigo pode ser encontrado aqui.

Até a próxima!

Sobre Willian Tuttoilmondo 15 Artigos
Arquiteto de software com mais de 25 anos de experiência em desenvolvimento de software, especialista em desenvolvimento multicamadas utilizando Embarcadero Delphi e NodeJS para o back-end e o próprio Delphi para o front-end. Usuário Linux desde 1998, é evangelista PostgreSQL, banco de dados no qual é especialista. Ocupa hoje a posição de arquiteto de software na TOTVS, a maior empresa de tecnologia do Brasil, além de ser sócio fundador da LT Digital Labs, empresa especializada em desenvolvimento e treinamentos.

Seja o primeiro a comentar

Faça um comentário

Seu e-mail não será publicado.


*