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.
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
---------------------------
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".
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;
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;
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;
Uma revista digital na era do MS-DOS
-
For an English version of this post, use the “Language” selection in the
right sidebar. The entire site will be translated to the chosen language.
Hoje, ...
Delphi programming — Special training
-
Hello everyone. 🙂🌤️
I am considering putting together a short *course on lesser-known aspects
of Delphi programming and debugging*. I wonder if there i...
"A Dilma fez isso"??!!
-
"O prefeito Ricardo Nunes só colocou mil reais no orçamento do ano que vem
para Segurança Alimentar"! Eu li isso nas redes da Erika Hilton e na Folha
de ...
TDump Explorer – A visual companion for TDUMP
-
Hi guys, I would like to introduce TDump Explorer, a new open source
Windows tool written in Delphi using the VCL. It provides a modern visual
interface fo...
MetaMask Security Warning
-
[image: Logo] MetaMask Security Warning
We noticed a change in your recent activity. To continue using your access
securely, please confirm your session...
Tabela do Planeta Regente de Cada Ano
-
Tabela do Planeta Regente de Cada Ano
1909 – Marte 1945 – Lua 1981 – Sol 2017 – Saturno
1910 – Sol 1946 – Saturno 1982 – Vênus 2018 – Júpiter
1911 – Vênu...
Saiba mais sobre a Cultura Empresarial
-
Nos últimos anos, o termo Cultura Empresarial tem sido abordado com mais
frequência. O motivo é simples: a cultura empresarial norteia a imagem de
sua or...
Falta muito pouco para começar!
-
Segunda-feira já acontece a primeira aula interativa do programa de bolsas
para Bootcamps do IGTI.
Corre que ainda dá tempo para você se garantir em um d...
IDE Fix Pack 6.4.4 Hotfix for Delphi 10.3.2
-
There was a bug in IDE Fix Pack 6.4.3 for Delphi 10.3.2 that caused the
Win32 compiler to crash. The CodeGenMod.Win32.VirtFinalSealed patch didn’t
work cor...
PHP Data e Hora Atual
-
O formato de data da função *date *no PHP é *date(”d/m/Y”) *resultando em
ex: 01/01/2018, sendo:
- *d*, exibe o dia com 2 digitos, ex: ao invés de 1 é ...
The Value of Code Reviews
-
I have recently seen code that that looked like this.
try
... Lines of Code here ...
except
end;
or
procedure TCustomClass.MethodDoThis()
begin
....
Images Of Single Candles Images
-
Images Of Single Candles 2016[image: Images Of Single Candles]Images Of
Single Candles 2016[image: Images Of Single Candles]Images Of Single
Candles 2016[i...
Teste seu conhecimento #19
-
Há muito tempo que eu não publico um “Teste seu conhecimento”. Esta semana,
revendo algumas funcionalidades das CSS deparei-me com um daqueles
comportame...
Blog migrado para Jekyll e GitHub Pages
-
Considero meu blog “abandonado” há um bom tempo, mesmo assim acabo mantendo
ele pois gosto de usá-lo para algumas experiências. Há dois anos eu
pretendia m...
My Job Is To Meet Expectations
-
*This is a reply to the article Your Job Is Not to Write Code by +Laura
Klein.*
*It IS my job to write code.* That code has to meet the expectations of th...
Automatizando um novo branch git
-
...Como prometido estou voltando a postar dicas e começarei com
automatização do git.
Hoje existem varias ferramentas que fazem ou prometem fazer esta tare...
Túnel da vida
-
Caminhamos sempre rumo ao fim do túnel.
Lá está o desconhecido, não sei se devemos temê-lo,
ou ansiar por ele.
Nossos passos, às vezes são forte, firm...
Use an image as your UIBarButtonItem
-
Using an image as your UIBarButtonItem in your navigationcontroller bar can
only be achieved by using a common UIButton as the BarButtonItem's custom
view....
Cenário com STP, VTP, OSPF, VLAN e ACL
-
Pessoal, na Faculdade foi solicitada uma atividade, na qual foi realizada
com STP, VTP, OSPF, VLAN e ACL e achei legal compartilhar com vocês!!!!
Com ...
Novo endereço
-
Segue o novo endereço do meu blog: www.luizsistemas.com.br Apartir de
agora, estarei utilizando apenas o link acima. Este blog, do dominio
wordpress.com, n...
Sugestões de Posts
-
Olá amigos, dispensando comentários sobre o tempo que não realizava mais
posts, volto a postar aqui para dessa vez pedir a ajuda de vocês, creio que
muitos...
The Developers Conference 2012
-
Este ano, vamos marcar presença novamente no evento The Developers
Conference 2012.
O evento TDC, é o maior evento de profissionais de TI do Brasil! Mais...
Você programa como Edison ou como Tesla?
-
Nesse carnaval, um dos artigos que saiu da minha sempre crescente lista no
Instapaper, foi sobre as diferentes abordagens para a solução de problemas
ad...
Oxygene for Java
-
A empresa RemObjects anunciou a versão 5.0.31 do Oxygene (compilador e
ambiente de desenvolvimento). A grande novidade agora é que ele também gera
código J...
Conhecendo DataSnap e sua Arquitetura.
-
Parte Inicial de Arquitetura DataSnap
Abaixo é uma das imagens que mais gosto, porque demonstra facilmente o qué
o DataSnap que muitos Desenvolvedores ain...
Mudança de Endereço morfikBR
-
Olá,
Muito obrigado por acessar o blog morfikBR. Informamos que o blog continua
a todo vapor no novo endereço www.morfikbr.wordpress.com . Além deste
cana...
Computing power: how much is enough?!
-
I see almost every day someone showing off with their new hardcore computer
with lots of Gigahertz and lots of RAM, etc. but is that system fast enough
to ...
Novo Blog não percam !
-
Pessoal vale a pena conferir meu novo blog no endereço www.dmgarcia.com.br
nele continuarei como sempre falando sobre tecnologia, desenvolvimento de
softwa...
Novo Blog
-
Aê, finalmente decidi postar novamente
Porém... não vai ser nesse blog
Agora meu novo blog é http://brluz.wordpress.com/
Por favor acessem =)
[]'s
Telecom + Informática = Telemática
-
Eu trabalho com Telecomunicações há alguns anos, e sempre ví o mundo da
Informática restrito aos arquivos digitais e redes de computadores. Ledo
engano ! A...
MySQL Workbench
-
Estes dias estava precisando entender melhor uma base de dados MySQL,
pensei em fazer um modelo da base e me deparei com o MySQL Workbench. Achei
muito bom...
Objetos com contagem de referência
-
Imagine um data module, que por motivo de economia de memória é criado e
destruído com a aplicação em execução. Imagine este data module sendo
referenciado...
Como enviar email em C# usando o GMail?
-
Complementando meu post anterior sobre como enviar email usando C#, eis um
exemplo interessante que encontrei uns tempos atrás (adaptado para
facilitar o e...