Use Class/Record Helpers no Delphi

Você sabe o que vem a ser um Class Helper?
Class Helper é uma técnica da programação orientada a objetos que tem o intuito de estender as funcionalidades de uma classe, sem que seja necessário utilizar herança e sem que seja necessário alterar a classe original.

Imagine o seguinte cenário. Em sua aplicação existe a classe TPessoa, que por algum motivo você não pode alterar a sua implementação, mas, necessita de um determinado método nesta classe, como por exemplo retornar a idade da pessoa a partir de sua data de nascimento.

Em situações normais, seria feito um método CalcularIdade() por exemplo e seria usado da seguinte forma:

{...}
  EdtIdade.Text := IntToStr(CalcularIdade(oPessoa.DataNasc));
{...}

Agora imagine se fosse desta forma:

{...}
  EdtIdade.Text := oPessoa.Idade.ToString;
{...}

Muito mais claro e limpo. Mas como fazer isso sem ter acesso a implementação da classe TPessoa? Simples, fazendo um Class Helper para a classe TPessoa.
Faremos um exemplo bem simples para mostrar o uso prático do recurso. Vamos começar pela interface da classe TPessoa:

type
  TPessoa = class
  private
    FDataNasc: TDate;
  public
    property DataNasc : TDate read FDataNasc write FDataNasc;
  end;

Usaremos somente o campo referente a data de nascimento, pois ele está no foco do nosso exemplo. Agora faremos o nosso Class Helper para a classe TPessoa:

type
  TPessoaHelper = class helper for TPessoa
  private
    function GetIdade: integer;
  public
    property Idade : integer read GetIdade;
  end;

implementation
uses
  DateUtils,
  System.SysUtils;

{ TPessoaHelper }

function TPessoaHelper.GetIdade: integer;
begin
  Result := YearsBetween(Self.DataNasc,Now);
end;

Note que para determinar que uma classe é um class helper de outra é necessário utilizar a diretiva helper e indicar a classe principal com a palavra reservada for.
Outro ponto que merece atenção é o fato de que, na classe helper, nós temos acesso a todos os atributos públicos da classe principal a partir da própria instância do helper, por esse motivo que foi possível acessar a propriedade DataNasc.

Pronto, agora a propriedade Idade já está disponível para todos os objetos do tipo TPessoa em units que fizerem referência a unit do class helper (caso o class helper TPessoaHelper tenha sido feito em uma unit diferente da unit da classe TPessoa).

Por exemplo:

uses
  unt_pessoa,
  unt_pessoa_helper;

procedure TForm1.Button1Click(Sender: TObject);
var
  oPessoa : TPessoa;
begin
  oPessoa := TPessoa.Create;
  try
    oPessoa.DataNasc := StrToDate('26/11/1986');
    EdtIdade.Text := IntToStr(oPessoa.Idade);
  finally
    oPessoa.Free;
  end;
end;

Mas nosso exemplo ainda não terminou. Note que ainda estou fazendo um type casting explícito na idade, como resolver isso? Utilizando Record Helper :). A partir do Delphi XE3, é possível criar helpers para tipos primitivos, como por exemplo o tipo integer.

Faremos um record helper para o tipo integer com o simples intuito de converter o inteiro para uma string:

TIntHelper = record helper for integer
public
  function ToString():String;
end;

implementation

function TIntHelper.ToString: String;
begin
  Result := IntToStr(Self);
end;

Agora sim, nosso exemplo está completo:

procedure TForm1.Button1Click(Sender: TObject);
var
  oPessoa : TPessoa;
begin
  oPessoa := TPessoa.Create;
  try
    oPessoa.DataNasc := StrToDate('26/11/1986');
    EdtIdade.Text := oPessoa.Idade.ToString;
  finally
    oPessoa.Free;
  end;
end;

Vale lembrar que utilizando o helper TIntHelper que criamos, abrimos margem para substituir a clássica conversão StrToInt para simplesmente utilizar a  nossa função ToString():

var
  iNumero : integer;
  sTexto  : string;
begin
  sTexto := 1.ToString; //sTexto = '1'
  iNumero := 2;
  sTexto := iNumero.ToString; //sTexto = '2'
end;

Mas antes de sair criando helpers para tipos conhecidos, verifique se o próprio Delphi já não disponibiliza um helper para o mesmo intuito. A unit SysUtils possui uma série de helpers para os tipos mais comumente utilizados.

Uma dúvida que sempre aparece quando se começa a utilizar class helpers é com relação a criação de mais de um helper para uma determinada classe. Por exemplo, o seguinte código não funciona da maneira que intuitivamente se espera:

  TPessoaHelper = class helper for TPessoa
  private
    function GetIdade: integer;
  public
    property Idade : integer read GetIdade;
  end;

  TPessoaFisicaHelper = class helper for TPessoa //esse passa a ser o Helper "ativo"
  public
    function isValidCPF():Boolean;
  end;

Não ocorre um erro de compilação, porém, a propriedade Idade, não estará mais disponível, pois um helper se sobrepõe ao outro. Para que isso não ocorra, é necessário indicar o class helper antecessor, desta forma:

  TPessoaHelper = class helper for TPessoa
  private
    function GetIdade: integer;
  public
    property Idade : integer read GetIdade;
  end;

  TPessoaFisicaHelper = class helper (TPessoaHelper) for TPessoa
  public
    function isValidCPF():Boolean;
  end;

  TPessoaJuridicaHelper = class helper (TPessoaFisicaHelper) for TPessoa
  public
    function isValidCNPJ():Boolean;
  end;

Devido a evoluções que a arquitetura do Delphi vem sofrendo, principalmente pelas inovações relacionadas a mobilidade, a própria Embarcadero recomenda que se use classe/record helpers para determinados fins, fazendo com que o seu código esteja protegido de eventuais mudanças mais trágicas, como por exemplo a promessa de extinção da concatenação de strings através do simbolo de adição (Ex: Str1 := ‘Texto ‘ + IntToStr(3));

Sobre Diego Garcia

Analista/Desenvolvedor Delphi desde 2008, bacharel em Ciência da Computação e entusiasta de metodologias ágeis e engenharia de software.
Esse post foi publicado em Programação e marcado , , . Guardar link permanente.

Uma resposta para Use Class/Record Helpers no Delphi

  1. Marcello Dias disse:

    Acabaram com o + no Dart depois voltaram atrás.
    Todo mundo sabe a merda que pode fazer usando ele dentro de um LOOP,mas só faz quem
    quer.
    Lembrando que o Delphi conviveu durante várias versões com um Bug relacionado a utilziação de Class Helpers,que só foi solucionado agora no Berlin.
    http://blog.marcocantu.com/blog/2016-june-closing-class-helpers-loophole.html

Deixe um comentário