Fala ai Radizeiro e Radizeira, tudo bem com você?
Você já deve ter sentido saudades da nossa série de generics né?
Então, demos uma pequena pausa para mostrar aqui no blog algumas das novas funcionalidades da versão 10.4 do Delphi, e vou te dizer, está espetacular, vale a pena conferir.
E hoje não podia faltar mais um post, e estaremos falando sobre construtores para generics, e também as constraints, que são as validações que podemos fazer na hora de tipificar uma classe com um objeto genérico.
Vamos lá então?
Dentro do nosso Delphi temos um projeto vazio.
Mas primeiro quero explicar para vocês alguns conceitos, algumas coisas básicas dentro do generic que pode facilitar o seu uso no seu dia a dia.
Então vamos colocar um botão como já de costume…rsrs
Vamos entrar nesse botão, e antes da nossa classe TForm, vamos criar as classes que iremos trabalhar, nossas classes genéricas, vamos dizer assim.
Então o que iremos ter, vamos criar um exemplo simples, a famosa classe TPessoa.
TPessoa = class private FNome: String; procedure SetNome(const Value: String); public property Nome : String read FNome write SetNome; end; ... procedure TPessoa.SetNome(const Value: String); begin FNome := Value; end;
Vamos usar essa classe pessoa como exemplo para outras classes genéricas.
Dentro dessa nossa estrutura podemos criar um exemplo simples de nota fiscal, que recebe um generics, que por exemplo, será o generics de TPessoa.
TNFe<T> = class FGeneric : T; function GetGenerics : T; end; ... function TNFe<T>.GetGenerics: T; begin Result := FGeneric; end;
Observe que criamos um método que retorna um tipo genérico, e para que possamos passar esse retorno de um tipo que não sabemos, foi criado uma variável também do tipo genérico.
Agora dentro do botão, para que você possa compreender melhor o funcionamento, irei criar uma variável do tipo da classe de nota fiscal, e essa minha classe espera um generics, como exemplo usamos TPessoa, instanciamos esse nosso objeto, como no código abaixo.
procedure TForm1.Button1Click(Sender: TObject); var NFe : TNfe<TPessoa>; begin NFe := TNfe<TPessoa>.Create; try finally NFe.Free; end; end;
Você observar a imagem logo abaixo, quando chamamos esse objeto ele possui propriedades que pertencem a TPessoa.
Sendo assim eu posso realizar a implementação, segundo o código logo a baixo.
procedure TForm1.Button1Click(Sender: TObject); var NFe : TNfe<TPessoa>; begin NFe := TNfe<TPessoa>.Create; try Nfe.FGeneric.Nome := 'Thulio'; finally NFe.Free; end; end;
Ao compilar o projeto e clicarmos no botão temos como resultado um Access Violation.
Esse erro ocorre no ato de atribuir um nome ao objeto.
Você pode estar se perguntando, mas não foi atribuído ao generic a classe TPessoa para a classe TNFe?
E você viu que em tempo de projeto é possível ter acesso aos atributos da classe TPessoa.
Realmente nós temos acessos aos atributos e métodos da classe TPessoa, mas se você reparar, TPessoa é uma classe, diferente de uma classe Record, essa classe não é criada e destruída sozinha, a classe TPessoa não está sendo instanciada em tempo nenhum, você tem que instanciar essa classe.
Essa classe precisa ser instanciada para que ela possa existir na memória, e você possa ter acesso aos seus atributos e métodos dessa classe.
Então, TPessoa não existe, eu não à criei, só disse que o genérico de TNFe é TPessoa, mas não foi instanciada, sendo assim ela não está endereçada na memória, precisamos então instanciar essa nossa classe TPessoa.
E é aí que entra os constructors para generics.
Dentro dessa classe de TNFe, nos códigos anteriores, você pode observar que só temos um método que retorna generic.
E dentro dessa classe eu irei precisar criar um constructor, observe como foi feito no código abaixo.
TNFe<T> = class FGeneric : T; constructor Create; function GetGenerics : T; end;
Agora a classe TNFe tem um método construtor, que por sua vez irá poder instanciar a classe passada para o generics da TNFe, quando eu chamar no meu botão do formulário TNFe.Create, ele estará chamado esse método construtor.
Até então nenhuma novidade para você que já está familiarizado com orientação a objetos, caso não tenha tenho diversos treinamentos voltados para tal conceito.
Dentro do método construtor da classe TNFe tenho que instanciar a variável FGenerics, criado dentro dessa nossa classe, você pode ver em códigos anteriores.
Essa variável não pode ser instanciar, porque o tipo T não é uma instância, sendo assim, fica impossibilitado de ser criado, mas podemos dizer para esse tipo genérico que ele pode ser criado passando para ele um constructor padrão, como pode ser visto no código abaixo, e dessa forma podemos instanciar nossa tipo genérico.
TNFe<T : constructor> = class FGeneric : T; constructor Create; function GetGenerics : T; end; ... constructor TNFe<T>.Create; begin FGeneric := T.Create; end;
Agora podemos instanciar esse objeto, pois definimos para ele um construtor padrão, desta forma FGeneric passa a existir quando é criada a classe TNFe, agora quando eu pedir para retornar o tipo genérico ele estará retornando uma instância ativa, fazendo assim podemos acessar os atributos e métodos dessa classe genérica, agora vamos compilar para verificar se irá correr tudo bem.
OBS.: Primeiro vamos colocar um ShowMessage dentro desse nosso botão para verificar se tudo irá funcionar devidamente.
procedure TForm1.Button1Click(Sender: TObject); var NFe : TNfe<TPessoa>; begin NFe := TNfe<TPessoa>.Create; try Nfe.FGeneric.Nome := 'Thulio'; ShowMessage(Nfe.FGeneric.Nome); finally NFe.Free; end; end;
Viu que legal?
Agora está funcionando perfeitamente.
Quando você precisar trabalhar com alguma classe, ou com algum tipo que tenha um construtor padrão, você precisa instanciar ele dentro da classe, passando o atributo constructor dentro da classe, como visto no código acima.
Outra coisa que você poderia fazer, e isso é um outro ponto, dentro da classe TNFe setar um class com um constructor, como no código abaixo.
TNFe<T : class, constructor> = class FGeneric : T; constructor Create; function GetGenerics : T; end;
Fazendo isso esse nosso exemplo irá continuar funcionando perfeitamente, porem se eu colocar, por exemplo, uma variável do tipo TNFe mas com o tipo genérico do tipo String, como o código abaixo, não será aceito pelo compilador.
procedure TForm1.Button1Click(Sender: TObject); var NFe : TNfe<TPessoa>; NFe : TNFe<String>; begin NFe := TNfe<TPessoa>.Create; try Nfe.FGeneric.Nome := 'Thulio'; ShowMessage(Nfe.FGeneric.Nome); finally NFe.Free; end; end;
É gerado um erro de compatibilidade pelo compilador.
Pois o parâmetro T está esperando um objeto do tipo class, ele aguarda uma classe e não um tipo primitivo.
Você pode tipificar, usar constraint, fazer uma validação do que você quer passar, iremos tirar esse class de dentro do generic, quando for rodar o projeto irá funcionar perfeitamente.
Agora podemos criar uma outra classe, onde irá receber um tipo genérico, e esse tipo genérico é um IInterface, fazendo assim, a partir desse momento ele irá receber qualquer tipo de interface, mas se eu tentar passar um class padrão para ele, ou diretamente uma classe, que não seja objeto de uma interface, ele não irá aceitar.
TTeste< T : IInterface> = class end;
Nessa nossa classe ela poderia receber como tipo um TComponent, onde ele irá receber somente tipos que sejam herdados do TComponent, essa é uma forma de você fazer uma validação, e aumentar sua segurança com generics.
Mesmo usando generics, você consegue manter uma segurança maior dentro do seu código, para que ninguém faça besteira dentro do seu projeto.
Esse foi mais um post da nossa série de generics, e espero que você tenha gostado, que tenha aberto mais os horizontes sobre generics.
Esse e muitos outros conteúdos você encontra dentro do nosso CLUBE DOS PROGRAMADORES DELPHI, a NETFLIX dos programadores Delphi, quer saber mais sobre o clube, então clique no link abaixo e vem fazer parte dessa história.
CLIQUE AQUI E SAIBA MAIS SOBRE O CLUBE DOS PROGRAMADORES DELPHI