Mostrando postagens com marcador rtti. Mostrar todas as postagens
Mostrando postagens com marcador rtti. Mostrar todas as postagens

quinta-feira, 5 de setembro de 2013

Escopo de Tipo Enumerado

Olá!

Eu gosto muito de usar tipos enumerados e existe um risco claro de haver ambiguidades.

Para evitar isso, existe a diretiva:

{$SCOPEDENUMS ON} ou {$SCOPEDENUMS OFF}

Para mais informações veja em:

http://docwiki.embarcadero.com/RADStudio/XE4/en/Scoped_Enums_(Delphi)

terça-feira, 21 de fevereiro de 2012

Trabalhando com Atributos (Custom Attributes)

Hoje gostaria de falar sobre Atributos, mais especificamente Custom Attributes e compartilhar uma experiência recente onde esta facilidade foi crucial.

Antes de mais nada quero esclarecer que não estou me referindo aos atributos de uma classe e sim de um conceito novo (Delphi 2010) mas já existente em outras plataformas de desenvolvimento.

Do que NÃO estamos falando:


TPessoa = class
private
  FNome: string; 
  FIdade: Integer; {<== Isto é um atributo de uma classe e NÃO é disto que estamos falando}
public
  property Nome: string read FNome write FNome;
  property Idade: Integer read FIdade write FIdade;
end;

Do que estamos falando:

TPessoa = class
private
  FNome: string; 
public
  property Nome: string read FNome write FNome;

  [TIdadeMinima(21)]   {<== É disto que estamos falando!}
  property Idade: Byte read FIdade write FIdade;
end;

Mas o que significa atributo?


No dicionário:
S. m. 1. Aquilo que é próprio de um ser. 2. Emblema distintivo; símbolo. 3. Característica, qualitativa ou quantitativa, que identifica um membro de um conjunto observado. ...
Ou seja, atributo é, basicamente, uma qualidade atribuída a um elemento.

Usando no Delphi

O uso mais recorrente é em frameworks ORM. Mas as possibilidades são muitos mais amplas que isso. Poderíamos utilizar esta facilidade para atender regras de formatação de um documento eletrônico, como por exemplo o EFD Pis/Cofins ou um protocolo de comunicação com algum equipamento.
Então, indo ao ponto, os atributos são classes descendentes de TCustomAttribute. O TCustomAttribute por sua vez não implementa nada de especial.
Seguindo o nosso exemplo inicial, o atributo TIdadeMinima seria declarado da seguinte maneira:
  TIdadeMinima= class(TCustomAttribute)
  private
    FIdadeMinima: Byte;
  public
    constructor Create(AIdadeMinima: Byte); 
    function IsIdadeMaior(AIdade: Byte): Boolean;
    property IdadeMinima: Byte read FIdadeMinima;
  end;

//Implementação//

{ TIdadeMinima }

constructor TIdadeMinima.Create(AIdadeMinima: Byte);
begin
  Self.FIdadeMinima := AIdadeMinima;
end;

function TIdadeMinima.IsIdadeMaior(AIdade: Byte): Boolean;
begin
  Result := AIdade >= Self.FIdadeMinima;
end;
No que devemos focar?
O construtor da classe deve solicitar os parâmetros que fazem parte do escopo do atributo. No exemplo, o atributo está especificando uma idade mínima para a instância de TPessoa. Portanto o constructor do atributo pede justamente a idade mínima.
Para efetivamente tiramos proveito do atributo temos que recorrer à RTTI, pois só com ela temos acesso a um atributo. No exemplo abaixo, temos o código completo de um programa console, que pede o nome e a idade do usuário e valida a idade.
program UsandoAtributos;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, Rtti;

type

  TIdadeMinima= class(TCustomAttribute)
  private
    FIdadeMinima: Byte;
  public
    constructor Create(AIdadeMinima: Byte);
    function IsIdadeMaior(AIdade: Byte): Boolean;
    property IdadeMinima: Byte read FIdadeMinima;
  end;

  TPessoa = class
  private
    FNome: string;
    FIdade: Byte;
  public
    property Nome: string read FNome write FNome;
    [TIdadeMinima(21)]
    property Idade: Byte read FIdade write FIdade;
  end;

{ TIdadeMinima }

constructor TIdadeMinima.Create(AIdadeMinima: Byte);
begin
  Self.FIdadeMinima := AIdadeMinima;
end;

function TIdadeMinima.IsIdadeMaior(AIdade: Byte): Boolean;
begin
  Result := AIdade >= Self.FIdadeMinima;
end;

var
oPessoa : TPessoa;
sNome   : string;
bIdade  : Byte;

_ctx    : TRttiContext;
_typ    : TRttiType;
_pro    : TRttiProperty;
oAtt    : TCustomAttribute;
begin
  Writeln('****************************');
  Writeln('*** Bem vindo ao sistema ***');
  Writeln('****************************');
  Writeln(EmptyStr);
  oPessoa := nil;
  try
    oPessoa := TPessoa.Create;
    Write('Digite o seu nome: ');
    Readln(sNome);
    oPessoa.Nome := sNome;

    Write('Digite a sua idade: ');
    Readln(bIdade);
    oPessoa.Idade := bIdade;

    Writeln(EmptyStr);
    Writeln('Dados');
    Writeln(oPessoa.Nome,' - ',oPessoa.Idade);

    _ctx := TRttiContext.Create;
    _typ := _ctx.GetType(oPessoa.ClassType);
    for _pro in _typ.GetProperties do
    begin
      for oAtt in _pro.GetAttributes do
      begin
        if oAtt is TIdadeMinima then
        begin
          Writeln('A idade mínima é de: ',TIdadeMinima(oAtt).IdadeMinima);
          if (TIdadeMinima(oAtt).IsIdadeMaior(oPessoa.Idade)) then
          begin
            Writeln('A idade de ',oPessoa.Nome,' passou!');
          end else
          begin
            Writeln('A idade de ',oPessoa.Nome,' nao passou!');
          end;
        end;
      end;
    end;
  finally
    if (Assigned(oPessoa)) then
    begin
      oPessoa.Free;
    end;
    _ctx.Free;
  end;
  Writeln(EmptyStr);
  Writeln('Tecle [ENTER] para encerrar');
  Readln;
end.
Vale ressaltar que os atributos podem ser atribuídos a qualquer elemento da sua classe. É o seu código RTTI que terá que ser moldado para tirar proveito delas. Outro ponto importante é que um elemento pode ter vários atributos.

Uso prático

Como mencionei no decorrer, muitos exemplos sobre este tema esta ligado diretamente com a ORM, onde um atributo para a classe indica uma tabela enquanto que alguns atributos para as propriedades seriam os campos e as constraints.
Mas no meu caso, onde precisei gerar um documento eletrônico (EFD Pis/Cofins) baseado em regras, eu criei uma classe base e para cada tipo de registro criei uma classe descendente com os campos e, principalmente, seus atributos: tamanho, tamanho exato, se somente números etc e etc. Por fim, na tal classe base, foi disponibilizado um método chamado GetLine que tanto valida as informações da classe quanto gera a linha correspondente. Esta solução foi ótima pois o código ficou mais limpo e tenho certeza que mudanças ou implementações futuras serão muito tranquilas.
Também poderíamos usar para implementar uma validação em uma tela de entrada, afinal, o TForm é uma classe como qualquer outra. Este seria um ótimo uso.

É isto.

quarta-feira, 27 de julho de 2011

Diminuindo o tamanho do seu executável

Olá. Recebi um link muito interessante sobre o tema.

Não sei se você percebeu mas com a nova RTTI disponível desde o Delphi 2010 os executáveis gerados ficam muito grande.

Se você não tira proveito da RTTI não precisa deste peso.

http://delphi.about.com/b/2011/07/26/create-smaller-delphi-xe-executables-remove-rtti-pack-exe.htm?nl=1

O segredo está na diretiva de compilação WEAKLINKRTTI que no estado "on" desliga a RTTI. :)

Se estivermos nesta situação e precisarmos da RTTI receberemos a seguinte erro:

---------------------------
Debugger Exception Notification
---------------------------
Project Small.exe raised exception class EInsufficientRtti with message 'Insufficient RTTI available to support this operation'.
---------------------------
Break Continue Help
---------------------------

Mais sobre o assunto:

http://docwiki.embarcadero.com/RADStudio/en/WEAKLINKRTTI_directive_(Delphi)

http://docwiki.embarcadero.com/RADStudio/en/RTTI_directive_(Delphi)

Veja um código simples:

program Small;

{$APPTYPE CONSOLE}

//Pulo do gato!
{$WEAKLINKRTTI ON}

uses
  SysUtils, Rtti;

type

  //Aqui temos um controle mais fino
  {$RTTI EXPLICIT METHODS([vcPublic])}
  TMinhaClasse = class(TObject)
  public
    procedure Teste;
  end;

{ TMinhaClasse }

procedure TMinhaClasse.Teste;
begin
  Writeln('Ola mundo!');
  Readln;
end;

var
LContext : TRttiContext;
LType    : TRttiType;
oTeste   : TMinhaClasse;
begin
  try
    oTeste := TMinhaClasse.Create;
    LContext := TRttiContext.Create;
    LType := LContext.GetType(TMinhaClasse);
    LType.GetMethod('Teste').Invoke(oTeste,[]);
    oTeste.Free;
  except
    on E: Exception do
    begin
      Writeln(E.ClassName, ': ', E.Message);
      Readln;
    end;
  end;
end.

segunda-feira, 12 de abril de 2010

Como executar um método pelo Nome

Olá!

Este é o tipo de dica que a gente acha que nunca vai usar.

:D

Eu uso o KBM para construir sistemas em N Camadas: http://www.components4programmers.com/.

Para a construção de um RPC ( estou indo direto ao ponto, em caso de dúvidas sobre o assunto me contacte ) a sugestão é encadear um monte de IF como no exemplo abaixo:


// Master request processor.
//--------------------------

function TkbmRPCDatabase.ProcessRequest(const Func:string; const ClientIdent:TkbmMWClientIdentity; const Args:array of Variant):Variant;
var
afunc : string;
begin
  Self.GerarLog(Format('Func: [%s]',[Func]), 0001, ctLOGGExpert,rlLow);

  try
    AFunc:=UpperCase(Func);
    if AFunc='TESTE' then
       Result := PerformTESTE(ClientIdent,Args)
    else Result:=inherited ProcessRequest(Func,ClientIdent,Args);
  except
    on E: Exception do
    begin
      Self.GerarLog(DescreverException(E),0001, ctLOGGExpert,rlLow );
      raise;
    end;
  end;
end;

Ou seja, se eu tiver n funções, teria que encadear um if..else que na minha opnião fica asqueroso de se dar manutenção além de obviamente não ser "performático".

A solução definitiva apareceu nesse tópico: http://delphi.about.com/cs/adptips2004/a/bltip0204_3.htm

Note o seguinte: O seu método tem que estar na parte published da sua classe. Em outras sessões não rola.

Veja como ficou o código:

// Master request processor.
//--------------------------

function TkbmRPC_CSAS.ProcessRequest(const Func:string; const ClientIdent:TkbmMWClientIdentity; const Args:array of Variant):Variant;
var
sFunc    : string;
rRoutine : TMethod;
_perform : TPerformFunction;
begin
  Self.GerarLog(Format('Executando: [%s] - [%s]',[Func,ClientIdent.RemoteLocation]),322382429,ctLOGInformation,rlMiddle);

  sFunc := Format('Perform%s',[Func]);
  rRoutine.Data := Pointer(Self);
  rRoutine.Code := Self.MethodAddress(sFunc);

  if (Assigned(rRoutine.Code)) then
  begin
    _perform := TPerformFunction(rRoutine);
    Result := _perform(ClientIdent,Args);
  end else
  begin
    inherited ProcessRequest(Func,ClientIdent,Args);
  end;

end;

Para isso tudo funcionar, tive que criar um tipo especial:

interface

uses
  kbmMWSecurity;

type

{$M+}

  ///Usado para permitir uma chamada dinâmica às funções dos RPCs
  TPerformFunction = function(ClientIdent:TkbmMWClientIdentity; const Args:array of Variant): Variant of object;

terça-feira, 30 de março de 2010

Verificando a hierarquia de uma determinada classe

Eventualmente queremos saber a hierarquia de uma determinada classe.

Eu lanço mão do seguinte algoritmo:

function GetClassHierarchy(poList: TStringList; oClassBase: TClass): Boolean;
var
oClass: TClass;
begin
  Result := False;
  if (Assigned(poList)) then
  begin
    poList.Clear;
    oClass := oClassBase;
    while (Assigned(oClass)) do
    begin
      poList.Insert(0,oClass.ClassName);
      oClass := oClass.ClassParent;
    end;
  end;
end;

Exemplo da utilização:

procedure TForm1.Button9Click(Sender: TObject);
var
slList : TStringList;
begin
  slList := TStringList.Create;
  Unt_RotinasLog.GetClassHierarchy(slList,TSQLQuery);
  Self.Memo1.Lines.Text := slList.Text;
  FreeAndNil(slList);
end;

Resultado para o exemplo acima:

TObject
TPersistent
TComponent
TDataSet
TWideDataSet
TCustomSQLDataSet
TSQLQuery

Show!

quarta-feira, 2 de setembro de 2009

Tipo Enumerado para String

Olá!

Eventualmente é necessário converter um tipo enumerado para string.

Dai você provavelmente pensaria em um código mais ou menos assim:

type
TMeuTipo = (mtOpcao1, mtOpcao2)

function Converter(peOpcao: TMeuTipo): string;
begin
case peOpcao of
mtOpcao1 : Result := 'mtOpcao1';
//E assim sucessivamente...
end;
end;


Imagine os problemas de manutenção para falar o mínimo...

Ao invés disso, use a RTTI a seu favor:

uses
TypInfo;

type
TMeuTipo = (mtOpcao1, mtOpcao2)

function Converter(peOpcao: TMeuTipo): string;
begin
//GetEnumName é a solução!
Result := GetEnumName(TypeInfo(TMeuTipo),Ord(peOpcao));
end;


P.S.: Um ótimo artigo sobre este assunto:

http://www.techtricks.com/delphi/enumname.php

terça-feira, 29 de julho de 2008

Type Cast

A conversão de tipo (type cast) é uma poderosa ferramenta e é o que nos permite tirar o real proveito de uma programação OO.

Eu opto pelo hardcast, mas para isso tem-se que tomar alguns cuidados.

Um exemplo geral de como faço é:



Um ótimo artigo sobre este assunto está em:

http://estacaozn.blogspot.com/2007/05/typecast-no-delphi-e-operadores-da-rtti.html

Minha lista de blogs