[Delphi] Usando uma classe sem usá-la (?!)

[Delphi] Usando uma classe sem usá-la

Que título estranho para um artigo, não?
Vou explicar melhor essa antítese. Quando precisamos trabalhar com uma instância de uma classe, devemos referenciá-la na seção uses da unit, correto? Bom, mais ou menos. O objetivo deste artigo é apresentar uma forma de utilizar uma classe sem necessariamente adicionar a sua referência na unit. O nome dessa mágica é RTTI!

 

André, RTTI de novo?
Sim! Apenas para esclarecer, pessoal: o RTTI é um universo de possibilidades. É bem provável que ainda haverá muitos artigos sobre este tópico aqui no blog. A ideia é explorar, aos poucos, a capacidade deste recurso para demonstrar as vantagens que podemos obter com a sua utilização.

A biblioteca de RTTI do Delphi disponibiliza um record chamado TRttiContext, do namespace System.Rtti. Por meio deste, ganhamos acesso às estruturas das classes que compõem o projeto. Para isso, utilizamos o método GetType informando o tipo como parâmetro, exemplificado no código abaixo:

var
  RttiContext: TRttiContext;
  RttiType: TRttiType;
begin
  RttiContext := TRttiContext.Create;
  RttiType := RttiContext.GetType(TMinhaClasse.ClassInfo);
 
  // Ações com o RttiType...
end;

 

Além do GetType, o record TRttiContext também fornece o método FindType para encontrar um tipo existente pelo nome, porém, com uma ressalva: o parâmetro deve vir acompanhado do nome da unit. Veja o exemplo a seguir:

var
  RttiContext: TRttiContext;
  RttiInstanceType: TRttiInstanceType;
begin
  RttiContext := TRttiContext.Create;
  RttiInstanceType := RttiContext.FindType('Unit1.TMinhaClasse').AsInstance;
 
  // Operações com RttiInstanceType...
end;

 

Vamos adiante. Para demonstrar essa funcionalidade, decidi abordar um exemplo prático.
Considere a simples unit a seguir, que declara uma classe com um único método para abrir o bloco de notas:

unit UnitMinhaClasse;
 
interface
 
type
  TMinhaClasse = class
  public
    procedure AbrirNotepad;
  end;
 
implementation
 
uses
  Windows, ShellAPI;
 
{ TMinhaClasse }
 
procedure TMinhaClasse.AbrirNotepad;
begin
  ShellExecute(0, 'open', 'Notepad.exe', nil, nil, SW_SHOWNORMAL);
end;
 
initialization
  TMinhaClasse.ClassName;
 
end.

Notou algo de incomum? A instrução no initialization, não é?
Pois bem, como a classe não é referenciada em outras units, o linker do Delphi tende a ignorá-la na produção do executável. Neste caso, obviamente, o método FindType do TRttiContext não será capaz de encontrá-la, portanto, adicionei uma simples instrução na seção initialization (um workaround) para que ela possa considerada pelo linker.

 

Agora, em outra unit, chamaremos o método dessa classe sem precisar referenciá-la na seção uses. Acredita ou não? 🙂
Para que isso seja possível, utilizaremos o GetMethod (para encontrar o método pela descrição) e o Invoke (para executar o método) das classes TRttiInstanceType e TRttiMethod, respectivamente.

var
  RttiContext: TRttiContext;
  RttiInstanceType: TRttiInstanceType;
  RttiMethod: TRttiMethod;
  Instance: TValue;
begin
  RttiContext := TRttiContext.Create;
 
  // Encontra o tipo
  RttiInstanceType := RttiContext.FindType('UnitMinhaClasse.TMinhaClasse').AsInstance;
 
  // Encontra o constructor (pelo nome "Create")
  RttiMethod := RttiInstanceType.GetMethod('Create');
 
  // Invoca o construtor, atribundo a instância à variável "Instance"
  Instance := RttiMethod.Invoke(RttiInstanceType.MetaclassType,[]);
 
  // Encontra o método "AbrirNotepad"
  RttiMethod := RttiInstanceType.GetMethod('AbrirNotepad');
 
  // Invoca o método
  RttiMethod.Invoke(Instance, []);
end;

Procedure de um objeto executado pelo Invoke do RTTI

Agora você pode acreditar! 😀

 

E se for necessário passar parâmetros?
Sem problemas! Na chamada do método Invoke, observe a presença dos colchetes. Dentro dele, podemos informar os parâmetros do método que será executado. Vamos ver como funciona?
Em TMinhaClasse, haverá um novo método, bem simples, para somar dois valores:

type
  TMinhaClasse = class
  public
    { ... }
    function Somar(const Valor1, Valor2: integer): string;
  end;
 
implementation
 
{ ... }
 
function TMinhaClasse.Somar(const Valor1, Valor2: integer): string;
begin
  result := 'Resultado: ' + IntToStr(Valor1 + Valor2);
end;

 

Em seguida, faremos o mesmo procedimento: executar o método da classe sem referenciar a unit.
O código é basicamente o mesmo apresentado acima, salvo que, dessa vez, informamos os parâmetros ao invocar o método. Como exemplo, indiquei os números 10 e 20 e optei por remover os comentários para facilitar a leitura:

var
  RttiContext: TRttiContext;
  RttiInstanceType: TRttiInstanceType;
  RttiMethod: TRttiMethod;
  Instance: TValue;
begin
  RttiContext := TRttiContext.Create;
  RttiInstanceType := RttiContext.FindType('UnitMinhaClasse.TMinhaClasse').AsInstance;
 
  RttiMethod := RttiInstanceType.GetMethod('Create');
  Instance := RttiMethod.Invoke(RttiInstanceType.MetaclassType,[]);
 
  RttiMethod := RttiInstanceType.GetMethod('Somar');
  ShowMessage(RttiMethod.Invoke(Instance, [10, 20]).AsString);
end;

Function de um objeto executado pelo Invoke do RTTI

 

Impressionante, não?
Faça proveito deste recurso do RTTI para criar bibliotecas de chamadas ou para evitar dependências explícitas entre units. Além disso, é um ótimo aliado ao se trabalhar com Generics.

Fico por aqui, leitores! Volto em breve com o próximo artigo da série sobre Design Patterns!
Abração!


 

Compartilhe!
Share on FacebookTweet about this on TwitterShare on LinkedInShare on Google+Pin on PinterestEmail this to someone

12 comentários

  1. Andrézão, muito legal isso ai cara. RTTI sempre foi temido por nós Delpheiros, mas acredito que essa técnica tem muito a agregar quando se trabalha com frameworks, BPPS (Boas Práticas em Produção de Software) e DSS (Desenvolvimento Sustentável de Software). No momento estou muito focado em Reflection para tentar reproduzir um bom ORM para Delphi como Realm é para Android e Swift.

    1. Opa, obrigado pelo comentário, Marcão!
      Devo confessar que eu mesmo desconhecia a capacidade do RTTI até há alguns meses. Hoje compreendo perfeitamente a maneira como um ORM é criado no Delphi. Siga firme nessa ideia do ORM! A comunidade Delphi agradece. 🙂
      A propósito, já assisti o vídeo sobre Clean Architecture. Vamos conversar em breve sobre isso!
      Abraço!

  2. Na verdade você cria a classe.
    Quado você invoca esse cara -> RttiInstanceType.GetMethod(‘Create’), você esta invocando o construtor da classe.
    Podemos fazer quase tudo com a RTTI nova no Delphi, rs.
    Outra coisa na passagem de parâmetro via RTTI você deve passar um array de TValue, e caso o tipo da varíavel seja diferente do método ele explode um invalid type cast, se não me engano rsrs.

    Uma dúvida, o initialization não esta errado?
    Eu uso esse cara: Classes.RegisterClass(XPTO).

    Mas parabéns pelo artigo =]

    1. Grande Oliveira! Mestre do RTTI! 🙂
      Exatamente, são 2 passos: invocar o construtor para obter uma instância da classe e depois executar o método desejado.
      O TMinhaClasse.ClassName no initialization é apenas uma instrução simbólica para que o linker inclua a classe no executável. A classe não precisa estar registrada com o RegisterClass, ao menos que alguma unit do projeto use o método GetClass para encontrá-la, mas não é o caso do FindType do RTTI.

      Obrigado, Oiveira!

  3. Sensacional, André!

    Mais uma vez os seus artigos – que são muito bem escritos, de maneira clara e com ótimos exemplos – seguem abrindo a mente dos desenvolvedores Delphi. Confesso que na prática ainda não faço uso do RTTI, o que conheço é apenas no campo da teoria. Mas após ler isso, confesso que estou bastante motivado a adotar o uso nos projetos aqui da empresa, pois o baixo acoplamento entre classes é uma batalha diária para quem procura programar seguindo as boas práticas.

    Forte abraço!

    1. Muito obrigado, Cleo!
      As bibliotecas de RTTI passaram por uma grande “transformação” nas versões mais recentes do RAD Studio. Meu objetivo é apresentar um pouco desse novo “paradigma” sempre que possível! 🙂
      Também compartilho dessa batalha para conquistar uma boa arquitetura. Acredito que, só pelo fato de termos essa preocupação, já estamos no caminho certo!

      Grande abraço!

  4. Caro Andre.
    Somos uma empresa de desenvolvimento de produtos para controle de acesso e ponto. Estamos procurando empresas ou profissionais para contratar o desenvolvimento de um aplicativo mobile. Como desenvolvemos nossos produtos em Delphi, o ideal seria mantermos nesta plataforma também para o desenvolvimento mobile.
    Acessei seu site via site da Embarcadero (MVPs).
    Caso esteja interessado em desenvolver nosso app, por favor entre em contato.
    Obrigado.

  5. Uma dúvida, André.
    E no final teria que dar FreeAndNil(RttiContext)? Ou é liberado dinamicamente da memória?

    1. Bom dia, Oteniel!
      Como o TRttiContext é um record, não é necessário liberá-lo. O próprio ARC (Automatic Reference Counting) do Delphi já se encarrega desse trabalho.

      Abraço!

Deixe uma resposta

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

Preencha o campo abaixo * Time limit is exhausted. Please reload CAPTCHA.