Gerar Classes – Que tal um ORM Básico? Parte 17

Ok, Ok! Somente na tecno… ops! Canal errado!

Como todos já estão cansados de saber, eu utilizo o projeto desta série em alguns trabalhos meus. E nestes, já algum tempo não sei o que são “inserts“, “updates” nem “deletes“, bem como não lembro do meu último “ParamByName” e muito menos “FieldByName“. Ponto para o nosso ORM Básico.

Porém, o ser humano é insaciável e sempre quer mais. E uma das prioridades que surgiram aqui, foi com relação à geração da classe. Para tal, eu vinha fazendo manualmente, ou seja, criava uma unit e então, a classe com todos as suas propriedades e atributos (nome da tabela, chaves primárias, etc.). E você, caro leitor, há de convir comigo que desenhar uma classe que representa uma tabela é uma coisa, mas quando isso passa a ser rotina, torna-se demasiadamente cansativo.

Foi então que eu resolvi tirar um tempinho e criar essa nova funcionalidade. Abaixo faço um resumo dos principais pontos. Ressaltando que, foi necessário fazer uma reformulação dos nomes das units visando melhorar a organização e evitar qualquer problema com outros projetos que tenho em minha máquina.

Novo Projeto no Grupo

Adicionado ao grupo de projetos um novo projeto para teste de geração de classe: ProjGerarClasse, com um formulário:

formGerarClasse

Contém:

  • Panel no topo com 2 Labels;
  • ListBox alinhando à esquerda;
  • Memo ao client;
  • Panel no rodapé com 3 Buttons.

Diagrama Gerar Classe

PrsGerarClasse

Antes de partir para o primeiro begin, eu sempre coloco no papel o que pretendo criar. Penso muito, principalmente na arquitetura das classes, e ainda assim, depois de finalizado o processo, sempre volto para refatorar alguma coisa (você é testemunha do que falo, ;) ). Analisemos o diagrama acima:

  • Demonstro de forma simples a utilização da injeção de dependência, tornando nosso código menos acoplado. Vemos isso na classe TGerarClasse. Nela, passamos o banco a ser utilizado (no caso criei uma para o Firebird – TGerarClasseBancoFirebird) no seu constructor, mas não diretamente, e sim através de uma interface. Veja que não tem ligação direta com a classe concreta do banco. Assim, poderemos criar uma para o SQL Server, por exemplo, e apenas adicionar ao projeto. Pronto! Não teremos qualquer problema para instanciá-la.
  • Descendo para as classes filhas, TGerarClasseIBX e TGerarClasseFireDac, foi onde me ative por mais tempo. Visto que estava realmente inclinado a utilizar composição em vez de herança. Porém, são funções idênticas, ou seja, dentro da classe filha teria uma “gerar alguma coisa”, que seria um objeto de TGerarClasse, o que pareceu ser redundante. Devido a isso, preferi ficar com herança neste caso. Quem sabe futuramente eu volte e reavalie minha decisão.
  • Em TDaoIBX e TDaoFireDac, inseri um novo método, GerarClasse, que instancia a classe concreta da respectiva suíte de componentes.

Implementação

Analisar é bom, porém, bom mesmo é codar!

Vamos direto ao ponto, ou seja, TGerarClasse:

  TGerarClasse = class
  private
    function Capitalize(ATexto: String): string;
  protected
    Resultado: TStringList;
    GerarClasseBanco: IBaseGerarClasseBanco;

    FTabela,
    FUnit,
    FClasse: string;

    function GetCamposPK: string; virtual; abstract;

    procedure GerarCabecalho;
    procedure GerarFieldsProperties; virtual; abstract;
    procedure GerarRodape;
  public
    constructor Create(AClasseBanco: IBaseGerarClasseBanco);
    destructor Destroy; override;

    function Gerar(ATabela, ANomeUnit: string;
      ANomeClasse: string = ''): string;
  end;

Como pode ser visto na sua declaração, temos tudo o que é preciso, ou seja, temos métodos para gerar o cabeçalho, os “fields” e “properties” e o rodapé da unit. Por ser uma classe abstrata, delega o que é específico de cada classe filha e implementa métodos comuns, como por exemplo:

procedure TGerarClasse.GerarCabecalho;
begin
  Resultado.clear;
  Resultado.add('unit ' + FUnit + ';');
  Resultado.add('');
  Resultado.add('interface');
  Resultado.add('');
  Resultado.add('uses');
  Resultado.add('  Lca.Orm.Base, Lca.Orm.Atributos;');
  Resultado.add('');
  Resultado.add('type');
  Resultado.add('  [attTabela(' + QuotedStr(FTabela) + ')]');
  Resultado.add('  T' + FClasse + ' = class(TTabela)');
end;

O cabeçalho da unit será sempre o mesmo, não se altera nas classes filhas. Da mesma forma, o método GerarRodape:

procedure TGerarClasse.GerarRodape;
begin
  Resultado.Add('  end;');
  Resultado.Add('');
  Resultado.Add('implementation');
  Resultado.Add('');
  Resultado.Add('end.');
end;

Agora vamos analisar a classe TGerarClasseIBX:

TGerarClasseIBX = class(TGerarClasse)
  private
    FDao: TDaoIBX;
  protected
    function GetCamposPK: string; override;

    procedure GerarFieldsProperties; override;

  public
    constructor Create(AClasseBanco: IBaseGerarClasseBanco; ADao: TDaoIBX);
  end;

Nesta, o destaque ficar por conta do constructor, cuja função é a de receber o objeto que implementa a interface IBaseGerarClasseBanco para repassar ao construtor da classe pai (TGerarClasse) e também receber o objeto Dao, que neste caso em questão, é o TDaoIBX. Não será necessário colocar o código do TGerarClasseFireDac, visto que o que muda é apenas o objeto (TDaoFireDac em vez de TDaoIBX).

Implementação do método GerarFieldsProperties (gerar as propriedades e fields da classe):

procedure TGerarClasseIBX.GerarFieldsProperties;
var
  Ds: TDataSet;
begin
  inherited;

  Ds :=  FDao.ConsultaSql(GerarClasseBanco.GetSQLCamposTabela(FTabela));

  GerarClasseBanco.GerarFields(Ds, Resultado);

  GerarClasseBanco.GerarProperties(Ds, Resultado, GetCamposPK);
end;

Nele, chamamos o objeto instanciado no construtor, relativo ao banco de dados selecionado, parar gerar os Fields e Properties.

Para finalizar, segue código do botão Gerar do formulário:

procedure TfrmConverte.btnGerarClick(Sender: TObject);
var
  Unidade,
  Tabela,
  Classe: string;
begin
  if lbTabelas.Count = 0 then
    Exit;

  Tabela  := trim(lbTabelas.Items[lbTabelas.ItemIndex]);
  Unidade := Tabela;
  Classe  := Tabela;

  memResult.lines.Text := dmPrin.Dao.GerarClasse(Tabela, Unidade, Classe);
end;

Eu coloquei o mesmo nome para tabela, unit e classe, mas poderiam ser diferentes caso eu desejasse.

Executando, selecionando uma tabela e clicando no botão gerar, obtemos:

GerarClasseExecuta

Para ver o código completo, basta baixar os fontes no GitHub.

Abraços.

Sobre o autor: Luiz Carlos (60 Posts)

Desenvolvedor de software Balsas/MA


Compartilhe:

Deixe uma resposta

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