Olá!

Estou de volta para mais um artigo desta série.

Gostaria de lembrar que por enquanto não estou disponibilizando os fontes. Mesmo porque, ainda não temos um código que faça jus o download. OK, confesso: quero que você quebre um pouco a cabeça, forçando a mente a trabalhar, melhorando a assimilação do conteúdo. :mad: Mas não se preocupe, em breve começarei a disponibilizar o link com os fontes.

Ajustes no código já trabalhado

Antes de iniciar, quero falar de uma pequena mudança que fiz no código do artigo anterior. Em Atributos.pas, eu alterei a classe TCampos, que antes era assim:

TCampos = class(TCustomAttribute)
private
  FTipo: TFieldTipo;
  FPK: Boolean;
public
  constructor Create(ATipo: TFieldTipo; APk: Boolean);
  function IsPk: Boolean; virtual;
end;

Agora, está assim:

TCampos = class(TCustomAttribute)
public
  function IsPk: Boolean; virtual;
end;

Na function IsPK do TCamposPK:

Resul := True;

Na unit Teste.pas, acima da property ID:

...
public
    [TCampoPk]
    property Id: Integer read FId write SetId;
...

Excluí o construtor e os campos internos (FTipo e FPK). O Constructor agora não tem mais parâmetros.

A alteração foi feita para simplificar um pouco as coisas e tirar campos que não estavam sendo utilizados. Como estamos no início, pode ser que eu volte atrás, mas por enquanto vamos levar da maneira que está.

Aqui está o código completo da unit Atributo.pas:

unit Atributos;

interface

uses
  Rtti;

type
  TNomeTabela = class(TCustomAttribute)
  private
    FNomeTabela: string;
  public
    constructor Create(ANomeTabela: string);
    property NomeTabela: string read FNomeTabela write FNomeTabela;
  end;

  TCampos = class(TCustomAttribute)
  public
    function IsPk: Boolean; virtual;
  end;

  TCampoPk = class(TCampos)
  public
    function IsPk: Boolean; override;
  end;

implementation

{ TCampos }

function TCampos.IsPk: Boolean;
begin
  Result := False;
end;

{ TCampoPk }

function TCampoPk.IsPk: Boolean;
begin
  Result := True;
end;

{ TNomeTabela }

constructor TNomeTabela.Create(ANomeTabela: string);
begin
  FNomeTabela := ANomeTabela;
end;

end.

Unidade Base

Temos que ter em mente que quanto mais abstrato for, melhor será o nosso ORM. Visto que, devemos tornar o processo de troca de um conjunto de componentes de acesso por outro o mais transparente possível. Vamos lá!

No Delphi, crie uma nova unit:

E salve com o nome de Base.pas.

O que nós queremos é poder ter acesso ao nosso banco de dados (Firebird) utilizando componentes como o IBX, UIB, DbExpress… sem tantos traumas, não é mesmo? Então, esta unit será a nossa base (é mesmo???) para alcançar este objetivo.

Vamos criar nossa primeira classe em Base.pas:

unit Base;

interface

uses Classes;

type
  IBaseDados = interface
  ['{9B0F9364-AB16-4C12-B4B7-4E2287840232}']
  end;

Na verdade, é uma interface e tem o objetivo de ser uma interface padrão para todos componentes databases (TUIBDataBase – UIB, TIBDatabase – IBX, etc.). Nela vemos o código GUID (identificador global único [globally unique identifier]) gerado através das teclas Ctrl+Shift+G.

Vamos criar mais uma interface:

  ITransacao = interface
  ['{2F1DCA7A-E7F4-4EC3-BDB2-22B99C8CA7DB}']
  end;

Esta interface será utilizada para os componentes “Transactions”, para controle das nossas transações.

Abaixo destas interfaces, vamos inserir um alias:

...
type
  IBaseDados = interface
  ['{9B0F9364-AB16-4C12-B4B7-4E2287840232}']
  end;

  ITransacao = interface
  ['{2F1DCA7A-E7F4-4EC3-BDB2-22B99C8CA7DB}']
  end;

  TTabela = class(TObject)
  end;
...

TTabela será uma classe sem propriedades e campos por nós definidos (pelo menos, não por enquanto). É claro que ela herda as propriedades e métodos de TObject.

Você pode estar se perguntando: já que não tem diferença para o TObject, o que justifica criar este alias?

Bom, o fato de necessitarmos do alias é que ele será utilizado em várias partes do nosso projeto, hora sendo uma classe pai de alguma outra classe hora sendo um parâmetro. Então, queremos restringir o uso destas classes, ou seja, que o parâmetro seja do tipo TTabela e não de outra classe qualquer derivada de TObject. Entendido? Ok!

Abra a unit Teste.pas. Altere a classe TTeste:

unit Teste;

interface

uses Base, Rtti, Atributos;

type
  [TNomeTabela('Teste')]
  TTeste = class (TTabela)
  private
    FHabitantes: Integer;
    FDescricao: string;
    FRendaPerCapta: Currency;
    FId: Integer;
    FData: TDateTime;
    FEstado: string;
    procedure SetData(const Value: TDateTime);
    procedure SetDescricao(const Value: string);
    procedure SetEstado(const Value: string);
    procedure SetHabitantes(const Value: Integer);
    procedure SetId(const Value: Integer);
    procedure SetRendaPerCapta(const Value: Currency);
  public
    [TCampoPk]
    property Id: Integer read FId write SetId;
    property Estado: string read FEstado write SetEstado;
    property Descricao: string read FDescricao write SetDescricao;
    property Data: TDateTime read FData write SetData;
    property Habitantes: Integer read FHabitantes write SetHabitantes;
    property RendaPerCapta: Currency read FRendaPerCapta write SetRendaPerCapta;
  end;

implementation


{ TTeste }

procedure TTeste.SetData(const Value: TDateTime);
begin
  FData := Value;
end;

procedure TTeste.SetDescricao(const Value: string);
begin
  FDescricao := Value;
end;

procedure TTeste.SetEstado(const Value: string);
begin
  FEstado := Value;
end;

procedure TTeste.SetHabitantes(const Value: Integer);
begin
  FHabitantes := Value;
end;

procedure TTeste.SetId(const Value: Integer);
begin
  FId := Value;
end;

procedure TTeste.SetRendaPerCapta(const Value: Currency);
begin
  FRendaPerCapta := Value;
end;

end.

Veja que agora ela descende de TTabela e não mais de TObject (não diretamente). Adicione a unit Base ao uses.

Pronto, agora que nós temos as interfaces para o database e o transaction, vamos criar um DAO abstrato:

  IDaoBase =  interface
    ['{D06AAE8D-D5F7-47E7-BF11-E26687C11900}']

    function Inserir(ATabela: TTabela): Integer;
    function Salvar(ATabela: TTabela): Integer;
    function Excluir(ATabela: TTabela): Integer;

    function InTransaction: Boolean;
    procedure StartTransaction;
    procedure Commit;
    procedure RollBack;

  end;

Veja que nossa interface declara os métodos básicos de um CRUD (inserção, deleção e salvamento), faltando apenas a recuperação de dados. Implantaremos isso nos próximos artigos da série. Veja também, que temos os métodos de controle das transações:

  • InTransaction: irá verificar se existe transação aberta;
  • StartTransaction: irá iniciar uma nova transação;
  • Commit: irá efetivar as alterações no banco de dados;
  • RollBack: irá cancelar a transação aberta.

Abaixo, código completo de Base.pas:

unit Base;

interface

uses Classes;

type
  IBaseDados = interface
  ['{9B0F9364-AB16-4C12-B4B7-4E2287840232}']
  end;

  ITransacao = interface
  ['{2F1DCA7A-E7F4-4EC3-BDB2-22B99C8CA7DB}']
  end;

  TTabela = class(TObject)
  end;

  IDaoBase =  interface
    ['{D06AAE8D-D5F7-47E7-BF11-E26687C11900}']

    function Inserir(ATabela: TTabela): Integer;
    function Salvar(ATabela: TTabela): Integer;
    function Excluir(ATabela: TTabela): Integer;

    function InTransaction: Boolean;
    procedure StartTransaction;
    procedure Commit;
    procedure RollBack;

  end;

implementation

end.

Diagrama atual do nosso projeto:

Chegamos ao fim deste artigo. No próximo, iremos iniciar a criação da classe específica do UIB.

Obrigado e até a próxima!

Desenvolvedor de software desde 1995. Em 1998, abriu sua própria empresa, a Lukas Sistemas, desde então passou a atender diversas empresas, principalmente autopeças. Apaixonado por Delphi, porém não o impede de flertar com outras linguagens sempre que possível. Mora na cidade de Balsas/MA com sua esposa e dois filhos.

Deixe um comentário

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

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.