Olá
Depois de alguns dias off, finalmente encontrei um tempinho para mais um artigo: DAO (Data Access Object).
O Delphi nos oferece muitas facilidades. Por exemplo, é possivel criar uma tela de cadastro sem utilizar uma linha de código sequer! Basta adicionar um DBGrid e um DBNavigator ligados a um dataset, e pronto! Isto pode ser muito bom para iniciantes e pequenos programas. Porém, para sistemas maiores, que exigem alto desempenho, preparados para as mudanças de requisitos e com um alto reuso de código, este tipo de facilidade pode tornar-se uma armadilha.
Desde o início, sempre procurei ao máximo não depender de componentes datawares, ou seja, DBEdits, DBComboEdits, DBNvigators, etc… Sempre tive a preopucação com a quantidade de recursos que são alocados em meus sistemas. Para tentar mimizar a carga, em grande parte, utilizo componentes desconectados (Edits, JvCalcEdit [jvc]…). É claro, não dá para fugir de tudo, como por exemplo, do ótimo DBGrid! É só não exagerar.
Outra vantagem de se trabalhar com os Edits e afins está relacionado aos comandos DML (insert, update, select, delete) dos Bancos de Dados Sql. Quando você mesmo se encarrega de criar as instruções Sql, tem a possibilidade de um controle maior sobre estes comandos, evitando gerar mais tráfego do que o necessário na rede, aumentando o desempenho como um todo.
CRUD – Create Read Update Delete
Uma das tarefas mais repetitivas que todo programador executa em sistemas que utilizam alguma base de dados é sem dúvida nenhuma as atividades relacionadas ao CRUD, em outras palavras, comandos para inserção, leitura(consulta), atualização e exlusão dos dados (persistência na base de dados).
Antigamente, eu criava um form com os botões responsáveis pelo CRUD. Ótimo, dava pro gasto! Porém, reuso de código que é bom, nada!
Eu sentia que o processo entre uma tela e outra era muito parecido, às vezes continham até o mesmo Sql. Quando um novo atributo surgia em determinada tabela, eu tinha que sair buscando no sistema os SQL’s desta tabela para inserir o novo campo. Isso é um trabalho desgastante e chato. E aí, volta e meia, lá vinha o cliente reclamando de erro em uma tela que antes funcionara perfeitamente, porém após uma última atualização passou a apresentar erro. Geralmente relacionado com a divergência entre o sistema e o banco de dados, ou seja, lembrei de atualizar uma sql e esqueci de outra.
Definindo o padrão
Eu não estava satisfeito com a forma como vinha trabalhando minhas telas de cadastro e isso me motivou a buscar novas técnicas.
Quando iniciei os estudos dos Padrões de Projeto e acabei me tornando um viciado em refatorar código (este artigo mesmo já foi várias vezes refatorado :)), conheci o DAO (na época pouca coisa relacionada ao Delphi, tendo os exemplos geralmente em Java) e senti que era um padrão interessante a ser utilizado em meus sistemas. No Wiki, possui a seguinte definição:
DAO (acrônimo de Data Access Object), é um padrão para persistência de dados que permite separar regras de negócio das regras de acesso a banco de dados. Numa aplicação que utilize a arquitetura MVC, todas as funcionalidades de bancos de dados, tais como obter as conexões, mapear objetos Java para tipos de dados SQL ou executar comandos SQL, devem ser feitas por classes de DAO.
Neste artigo não abordarei os temas MVC (Model View Controller), MVP (Model View Presenter) e nem MVVM (Model View ViewModel). Apenas DAO simplesmente.
Projeto sem o DAO
Para mostrar na prática, como de costume, irei pegar um projeto (simples) que possui um form e um datamodule que não utiliza o conceito e então refatorar o código, transformando-o em um projeto com DAO. No fim do artigo, irei disponibilizar os fontes. Vamos lá!
O banco de dados a ser utilizado segue o seguinte esquema (neste caso, Firebird 2.1):
Como pode ser visto, nosso Bd possui apenas duas tabelas, uma view, duas procedures e um generator. A tabela irá junto com os fontes.
Para o exemplo, utilizei a biblioteca IBX que acompanha o Delphi. Veja o projeto em execução:
Duplo clique sobre o registro no Dbgrid e vamos para a aba de lançamento:
Vemos o formulário principal, contendo um PageControl com duas abas (Lista contas a receber e Lançamento) e botões para a incluir, pesquisar, salvar, cancelar (a edição) e excluir.
O ponto principal deste projeto são os códigos dos botões. Porém, para melhor entendimento, segue código completo do formulário:
type TOperacao = (tpNone, tpIncluir, tpSalvar); TfrmPrincipal = class(TForm) pcPrincipal: TPageControl; tabLista: TTabSheet; tabLancto: TTabSheet; dsRec: TDataSource; dbgLista: TDBGrid; edID: TEdit; edDocumento: TEdit; edCliente: TEdit; edNomeCliente: TEdit; edEmissao: TEdit; edVencimento: TEdit; edValor: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; toolBarraPrincipal: TToolBar; btnIncluir: TToolButton; btnPesquisar: TToolButton; btnSalvar: TToolButton; ImageList1: TImageList; btnExcluir: TToolButton; btnCancelar: TToolButton; procedure FormCreate(Sender: TObject); procedure btnIncluirClick(Sender: TObject); procedure dbgListaDblClick(Sender: TObject); procedure btnSalvarClick(Sender: TObject); procedure btnExcluirClick(Sender: TObject); procedure btnCancelarClick(Sender: TObject); procedure btnPesquisarClick(Sender: TObject); procedure edDocumentoChange(Sender: TObject); procedure pcPrincipalChange(Sender: TObject); private FOperacao: TOperacao; { Private declarations } procedure ClearFields; procedure SetOperacao(const Value: TOperacao); protected public { Public declarations } property Operacao : TOperacao read FOperacao write SetOperacao; end; var frmPrincipal: TfrmPrincipal; implementation uses uDmPrinc; {$R *.dfm} procedure TfrmPrincipal.FormCreate(Sender: TObject); begin dm.sqlReceber.Open; pcPrincipal.ActivePageIndex := 0; Operacao := tpNone; ClearFields; end; procedure TfrmPrincipal.btnIncluirClick(Sender: TObject); begin pcPrincipal.ActivePageIndex := 1; ClearFields; Operacao := tpIncluir; end; procedure TfrmPrincipal.ClearFields; var i: integer; begin for i := 0 to ComponentCount - 1 do begin if Components[i] is TEdit then TEdit(Components[i]).Clear end; end; procedure TfrmPrincipal.dbgListaDblClick(Sender: TObject); begin with dm.sqlReceber do begin edID.text := fieldbyname('id').AsString; edDocumento.Text := fieldbyname('documento').AsString; edCliente.Text := fieldbyname('clienteid').AsString; edNomeCliente.Text := fieldbyname('nomecliente').AsString; edEmissao.Text := fieldbyname('emissao').AsString; edVencimento.Text := fieldbyname('vencimento').AsString; edValor.Text := formatfloat(',0.00', fieldbyname('valor').ascurrency); end; pcPrincipal.ActivePageIndex := 1; end; procedure TfrmPrincipal.SetOperacao(const Value: TOperacao); begin FOperacao := Value; btnIncluir.Enabled := FOperacao = tpNone; btnSalvar.Enabled := (FOperacao in [tpIncluir, tpSalvar]); btnExcluir.Enabled := FOperacao = tpNone; btnPesquisar.Enabled := FOperacao = tpNone; btnCancelar.Enabled := not (FOperacao = tpNone); end; procedure TfrmPrincipal.btnSalvarClick(Sender: TObject); var _Reg: integer; begin if not dm.ibTrans.InTransaction then dm.ibTrans.StartTransaction; try with dm.exec do begin if Operacao = tpIncluir then begin _Reg := dm.gerarID('gen_receberid'); close; SQL.Clear; sql.Add('insert into receber values ('); sql.Add(':id, :documento, :clienteid, :emissao, :vencimento, :valor)'); ParamByName('id').AsInteger := _Reg; ParamByName('documento').asstring := edDocumento.Text; ParamByName('clienteid').asstring := edCliente.Text; ParamByName('emissao').asstring := edEmissao.Text; ParamByName('vencimento').asstring := edVencimento.Text; ParamByName('valor').AsCurrency := StrToFloat(edValor.Text); ExecQuery; edID.Text := IntToStr(_Reg); end else begin close; SQL.Clear; sql.Add('update receber set documento=:documento, clienteid=:clienteid,'); SQL.Add('emissao=:emissao, vencimento=:vencimento, valor=:valor'); sql.Add('where id=:id'); ParamByName('documento').asstring := edDocumento.Text; ParamByName('clienteid').asstring := edCliente.Text; ParamByName('emissao').asstring := edEmissao.Text; ParamByName('vencimento').asstring := edVencimento.Text; ParamByName('valor').AsCurrency := StrToFloat(edValor.Text); ParamByName('id').AsString := edID.Text; ExecQuery; end; end; dm.ibTrans.Commit; Operacao := tpNone; dm.sqlReceber.Close; dm.sqlReceber.Open; ShowMessage('Registro salvo!'); except on e: Exception do begin dm.ibTrans.Rollback; btnCancelarClick(nil); Application.ShowException(e); end; end; end; procedure TfrmPrincipal.btnExcluirClick(Sender: TObject); begin if pcPrincipal.ActivePageIndex=0 then begin dbgListaDblClick(nil); end; if MessageDlg('Tem certeza que deseja continuar?',mtConfirmation,[mbYes,mbNo],0)=mrno then exit; try if not dm.ibTrans.InTransaction then dm.ibTrans.StartTransaction; try with dm.exec do begin close; SQL.Clear; sql.Add('delete from receber where id=:id'); ParamByName('id').AsString := edID.text; ExecQuery; end; dm.ibTrans.Commit; dm.sqlReceber.Close; dm.sqlReceber.Open; ShowMessage('Registro exlcuído!'); except on e: Exception do begin dm.ibTrans.Rollback; Application.ShowException(e); end; end; finally btnCancelarClick(nil); end; end; procedure TfrmPrincipal.btnCancelarClick(Sender: TObject); begin ClearFields; pcPrincipal.ActivePageIndex := 0; Operacao := tpNone; end; procedure TfrmPrincipal.btnPesquisarClick(Sender: TObject); var _Documento: string; begin _Documento := InputBox('Informe o número do documento','Documento',''); try try with dm.sqlReceber do begin close; SQL.Clear; if Trim(_documento)='' then sql.Add('select * from vw_receber') else begin sql.Add('select * from vw_receber where documento = :documento'); ParamByName('documento').AsString := UpperCase(_Documento); end; Open; if recordcount = 0 then begin ShowMessage('Registro não encontrado!'); end; end; except on e: Exception do begin Application.ShowException(e); end; end; finally btnCancelarClick(nil); end; end; procedure TfrmPrincipal.edDocumentoChange(Sender: TObject); begin if Trim(edID.Text) <> '' then begin Operacao := tpSalvar; end; end; procedure TfrmPrincipal.pcPrincipalChange(Sender: TObject); begin if pcPrincipal.ActivePageIndex = 0 then if Operacao <> tpNone then btnCancelarClick(nil); end; end.
Observe que o nosso formulário além de mostrar os componentes visuais na tela, tem a responsabilidade de manipular os dados diretamente em nosso banco de dados, ou seja, é muita responsabilidade atribuída ao form.
Você pode estar pensando: puxa, mas está funcionando que é uma beleza!
Sim, num primeiro momento funciona muito bem. Porém, os requisitos mudam! Disto não temos como fugir. Então imagine que, em dado momento, surge a necessidade de ter uma tela, além dessa já criada, para gerar vários contas a receber num mesmo processo, como por exemplo, no parcelamento de uma venda.
Nesta nova tela, você iria necessitar basicamente dos mesmos códigos CRUD. Estes códigos estariam num loop, mas a ideia seria a mesma.
Desta forma, já haveria duplicação de código em seu sistema. O que se configura como um sistema com baixa reusabilidade de código. Digamos que surja um novo requisito, ou melhor, um novo atributo na tabela Receber. Olha o problema! A manutenção começa a complicar.
Este é um exemplo bem simples, mas num sistema complexo o custo de manutenção seria elevado! Além disso, o programa não estará preparado para crescer e o seu tempo de vida será minimizado.
Refatorando o Sistema
Agora que definimos o problema, como resolver?
POO (Programação Orientado a Objetos) e Padrões de Projeto, com certeza!
Como eu disse, iremos nos concentrar no DAO, mas saiba que existem vários caminhos, vários padrões de projeto, vários meios de adotar um mesmo padrão! A forma que adotei para o DAO é a minha sugestão pessoal.
O primeiro passo, será criar a nossa classe TReceber. Para isto, vamos criar uma nova unit chamada uReceber.pas:
unit uReceber; interface uses uBase, uDmPrinc, db, IBQuery, Classes, Forms; type TReceber = class private FValor: Currency; FClienteid: integer; FId: Integer; FDocumento: string; FVencimento: TDateTime; FEmissao: TDateTime; procedure SetClienteid(const Value: integer); procedure SetDocumento(const Value: string); procedure SetEmissao(const Value: TDateTime); procedure SetId(const Value: Integer); procedure SetValor(const Value: Currency); procedure SetVencimento(const Value: TDateTime); public property Id: Integer read FId write SetId; property Documento: string read FDocumento write SetDocumento; property Clienteid: integer read FClienteid write SetClienteid; property Emissao: TDateTime read FEmissao write SetEmissao; property Vencimento: TDateTime read FVencimento write SetVencimento; property Valor: Currency read FValor write SetValor; end; TRecDao = class private class function ComandoSql(AReceber: TReceber): Boolean; public {métodos CRUD (Create, Read, Update e Delete) para manipulação dos dados} class function Insert(AReceber: TReceber): Boolean; //create class function Read(AQuery: TIBQuery; ADocumento: string): integer; class function Update(AReceber: TReceber): Boolean; class function Delete(AID: Integer): Boolean; end; implementation uses IBSQL, SysUtils; { TReceber } procedure TReceber.SetClienteid(const Value: integer); begin FClienteid := Value; end; procedure TReceber.SetDocumento(const Value: string); begin FDocumento := Value; end; procedure TReceber.SetEmissao(const Value: TDateTime); begin FEmissao := Value; end; procedure TReceber.SetId(const Value: Integer); begin FId := Value; end; procedure TReceber.SetValor(const Value: Currency); begin FValor := Value; end; procedure TReceber.SetVencimento(const Value: TDateTime); begin FVencimento := Value; end; { TRecDao } class function TRecDao.ComandoSql(AReceber: TReceber): Boolean; begin Result := false; if not dm.ibTrans.InTransaction then dm.ibTrans.StartTransaction; try with dm.exec do begin Close; sql.clear; SQL.Add('execute procedure receber_iu('); sql.Add(':id, :documento, :clienteid, :emissao, :vencimento, :valor)'); ParambyName('id').AsInteger := AReceber.Id; ParambyName('documento').asstring := AReceber.Documento; ParambyName('clienteid').AsInteger := AReceber.Clienteid; ParambyName('emissao').AsDateTime := AReceber.Emissao; ParambyName('vencimento').AsDateTime := AReceber.Vencimento; ParambyName('valor').AsCurrency := AReceber.Valor; ExecQuery; end; result := true; except on e: Exception do begin dm.ibTrans.Rollback; Application.ShowException(e); end; end; end; class function TRecDao.Delete(AID: Integer): Boolean; begin Result := False; if not dm.ibTrans.InTransaction then dm.ibTrans.StartTransaction; try with dm.exec do begin close; SQL.Clear; sql.Add('delete from receber where id=:id'); ParamByName('id').AsInteger := AId; ExecQuery; end; dm.ibTrans.Commit; dm.sqlReceber.Close; dm.sqlReceber.Open; Result := True; except on e: Exception do begin dm.ibTrans.Rollback; Application.ShowException(e); end; end; end; class function TRecDao.Insert(AReceber: TReceber): Boolean; begin AReceber.Id := dm.gerarID('gen_receberid'); result := ComandoSql(AReceber); end; class function TRecDao.Update(AReceber: TReceber): Boolean; begin result := ComandoSql(AReceber); end; class function TRecDao.Read(AQuery: TIBQuery; ADocumento: string): integer; begin with AQuery do begin close; sql.clear; sql.Add('Select * from vw_receber'); if Trim(ADocumento)<>'' then begin sql.Add('where documento=:documento'); Params[0].AsString := UpperCase(ADocumento); end; Open; result := recordcount; end; end; end.
Note que além da classe TReceber, temos uma classe com métodos estáticos, que é a nossa classe TRecDao. Desta forma, não precisaremos instanciar um novo objeto toda vez que necessitarmos dos métodos CRUD.
Observe que centralizei os comando sql de insert e update num mesmo método (ComandoSql), que faz uso de uma StoreProcedure. Será ela quem irá definir se é uma inclusão ou atualização. Veja a DDL desta procedure:
SET TERM ^ ; CREATE OR ALTER PROCEDURE RECEBER_IU ( id integer, documento varchar(20), clienteid integer, emissao timestamp, vencimento timestamp, valor decimal(15,2)) as begin if (exists(select id from receber where (id = :id))) then update receber set documento = :documento, clienteid = :clienteid, emissao = :emissao, vencimento = :vencimento, valor = :valor where (id = :id); else insert into receber ( id, documento, clienteid, emissao, vencimento, valor) values ( :id, :documento, :clienteid, :emissao, :vencimento, :valor); end^ SET TERM ; ^
Ah! Além da unit uReceber.pas, criei uma outra chamada uBase.pas, para guardar as classes base, variáveis e constantes globais, caso seja necessário. A princípio, teremos apenas um tipo definido:
type TOperacao = (tpInsert, tpUpdate, tpNone);
Servirá para controlar os estados do nosso formulário. Não coloquei diretamente na unit uReceber, visto que num sistema real não teríamos apenas a classe TReceber, muito pelo contrário, teríamos várias outras classes que necessitariam ter acesso à unit uBase.
Agora vamos alterar o nosso formulário principal:
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, Grids, DBGrids, DB, ComCtrls, ImgList, ToolWin, StdCtrls, uBase, uReceber; type TfrmPrincipal = class(TForm) pcPrincipal: TPageControl; tabLista: TTabSheet; tabLancto: TTabSheet; dsRec: TDataSource; dbgLista: TDBGrid; edID: TEdit; edDocumento: TEdit; edCliente: TEdit; edNomeCliente: TEdit; edEmissao: TEdit; edVencimento: TEdit; edValor: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; Label6: TLabel; toolBarraPrincipal: TToolBar; btnIncluir: TToolButton; btnPesquisar: TToolButton; btnSalvar: TToolButton; ImageList1: TImageList; btnExcluir: TToolButton; btnCancelar: TToolButton; procedure FormCreate(Sender: TObject); procedure btnIncluirClick(Sender: TObject); procedure dbgListaDblClick(Sender: TObject); procedure btnSalvarClick(Sender: TObject); procedure btnExcluirClick(Sender: TObject); procedure btnCancelarClick(Sender: TObject); procedure btnPesquisarClick(Sender: TObject); procedure edDocumentoChange(Sender: TObject); procedure pcPrincipalChange(Sender: TObject); procedure FormDestroy(Sender: TObject); private FOperacao: TOperacao; FReceber: TReceber; { Private declarations } procedure ClearFields; procedure SetOperacao(const Value: TOperacao); procedure SetReceber; protected public { Public declarations } property Operacao : TOperacao read FOperacao write SetOperacao; end; var frmPrincipal: TfrmPrincipal; implementation uses uDmPrinc; {$R *.dfm} procedure TfrmPrincipal.FormCreate(Sender: TObject); begin dm.sqlReceber.Open; FReceber := TReceber.Create; pcPrincipal.ActivePageIndex := 0; Operacao := tpNone; ClearFields; end; procedure TfrmPrincipal.SetReceber; begin with FReceber do begin Id := StrToInt(edID.Text); Documento := edDocumento.Text; Clienteid := StrToInt(edCliente.text); Emissao := StrToDateTime(edEmissao.Text); Vencimento := StrToDateTime(edVencimento.Text); Valor := StrToFloat(edValor.Text); end; end; procedure TfrmPrincipal.btnIncluirClick(Sender: TObject); begin pcPrincipal.ActivePageIndex := 1; ClearFields; Operacao := tpInsert; end; procedure TfrmPrincipal.ClearFields; var i: integer; begin for i := 0 to ComponentCount - 1 do begin if Components[i] is TEdit then TEdit(Components[i]).Clear end; end; procedure TfrmPrincipal.dbgListaDblClick(Sender: TObject); begin with dm.sqlReceber do begin edID.text := fieldbyname('id').AsString; edDocumento.Text := fieldbyname('documento').AsString; edCliente.Text := fieldbyname('clienteid').AsString; edNomeCliente.Text := fieldbyname('nomecliente').AsString; edEmissao.Text := fieldbyname('emissao').AsString; edVencimento.Text := fieldbyname('vencimento').AsString; edValor.Text := formatfloat(',0.00', fieldbyname('valor').ascurrency); end; pcPrincipal.ActivePageIndex := 1; end; procedure TfrmPrincipal.SetOperacao(const Value: TOperacao); begin FOperacao := Value; btnIncluir.Enabled := FOperacao = tpNone; btnSalvar.Enabled := (FOperacao in [tpInsert, tpUpdate]); btnExcluir.Enabled := FOperacao = tpNone; btnPesquisar.Enabled := FOperacao = tpNone; btnCancelar.Enabled := not (FOperacao = tpNone); end; procedure TfrmPrincipal.btnSalvarClick(Sender: TObject); var _Result: Boolean; begin // grava dados dos edits no objeto FReceber; SetReceber; if Operacao = tpInsert then _Result := TRecDao.Insert(FReceber) else _Result := TRecDao.Update(FReceber); if _Result then begin dm.sqlReceber.Close; dm.sqlReceber.Open; ShowMessage('Registro salvo!'); end; Operacao := tpNone; end; procedure TfrmPrincipal.btnExcluirClick(Sender: TObject); begin if pcPrincipal.ActivePageIndex=0 then begin dbgListaDblClick(nil); end; if MessageDlg('Tem certeza que deseja continuar?',mtConfirmation,[mbYes,mbNo],0)=mrno then exit; try TRecDao.Delete(StrToInt(edID.Text)); finally btnCancelarClick(nil); end; end; procedure TfrmPrincipal.btnCancelarClick(Sender: TObject); begin ClearFields; pcPrincipal.ActivePageIndex := 0; Operacao := tpNone; end; procedure TfrmPrincipal.btnPesquisarClick(Sender: TObject); begin TRecDao.Read(dm.sqlReceber, InputBox('Informe o número do documento','Documento','')); end; procedure TfrmPrincipal.edDocumentoChange(Sender: TObject); begin if Trim(edID.Text) <> '' then begin Operacao := tpUpdate; end; end; procedure TfrmPrincipal.pcPrincipalChange(Sender: TObject); begin if pcPrincipal.ActivePageIndex = 0 then if Operacao <> tpNone then btnCancelarClick(nil); end; procedure TfrmPrincipal.FormDestroy(Sender: TObject); begin FReceber.Free; end; end.
Basicamente, adicionei ao uses as units uReceber e uBase, criei um objeto FReceber e uma procedure SetReceber que irá setar as propriedades do objeto com os valores dos Edits, e por fim, tirei os comandos Sql do form. Menos uma responsabilidade com que ele terá que lhe dar, visto que deixamos a classe TRecDao encarregado do CRUD.
Se precisarmos manipular os dados da tabela Receber em outra parte do nosso projeto, bastará instanciar a classe TReceber, assim como foi feito acima, setar suas propriedades e fazer a persistência utilizando o TRecDao. Note que eu não precisei instanciar esta última, visto que seus métodos são estáticos.
Poderemos utilizar tanta vezes quanto for necessário este processo, ou seja, reuso de código total!
Poderíamos tratar de forma diferente as transactions (prevendo múltiplos updates) e tirar a dependência do datamodule em nossa classe TReceber, melhorando ainda mais a abstração, criando e retornando os datasets diretamente na classe. Porém, como eu disse, esta é uma sugestão de uso do DAO. A partir daqui, você poderá explorar novos meios, novas técnicas.
Código Fonte: http://dl.dropbox.com/u/478707/BlogDoLuiz-Dao.zip
Fico por aqui. Críticas e sugestões, comentem abaixo. Se gostou deste post, clique no botão Curtir abaixo.
Abraços.
Luiz, o artigo está muito bom, mas ficaria melhor e mais interessante se implementasse algo que fizesse uso das novidades inseridas no Delphi nas ultimas versões como generics, nova rtti, atributos…
Olá Daniel, obrigado pela participação.
Bom, realmente seria muito interessante abordar os temas que você citou. Infelizmente o período de testes do meu trial XE2 expirou e eu não tenho como fazer artigos nesta IDE. Pelo menos não até conseguir recursos para adquirir o XE2. Estou na batalha.
Eu já até abordei um pouco sobre Generics, e no comentário do post Clonando um Objeto, coloquei um link para um artigo muito bom sobre RTTI.
Abraços.
Muito bom o artigo, Luiz !
O Delphi não é a minha ferramenta de trabalho, mas sempre gostei dela e estou procurando aprender fazendo alguns projetos pequenos para alguns amigos, e não dá pra fugir da O.O.P.
Deixa eu fazer uma pergunta dá pra fazer isso utilizando apenas dbexpress, nada de DAO ou os dois se complementariam ?
Olá Sérgio
O importante aqui é entender o papel de cada um.
O Dbexpress é um conjunto de componentes que permite, de forma leve e rápida, acesso a diversos bancos de dados. Poderíamos dizer que ele tem basicamente a mesma função do IBX, porém, não é específico como o IBX o é (Interbase/Firebird). Assim como o IBX, você pode desenvolver aplicações com o Dbexpress sem utilizar um padrão de projeto. Contudo, note que no artigo eu tentei mostrar o problema de se ignorar as boas práticas de programação.
Já o DAO é um padrão de projeto, um Design Pattern. Os padrões de projetos permitem reaproveitar soluções previamente testadas e aprovadas. O DAO nos ajuda a seguir um padrão para as operações CRUD que desejamos efetuar em nossa base de dados.
Eu utilizei o IBX, mas poderia ter utilizado o Dbexpress, sem problema nenhum.
Abraços.
Gostaria de dar parabéns pelo artigo, é difícil encontrar um exemplo completo como o seu. Obrigado pela contribuição!
Fico feliz que tenha gostado, Luciano.
Valeu irmao, vai pra lista de favoritos aqui para me ajudar com ideias em novos projetos. Obrigado pela demonstração.
Primeiramente, gostaria de agradecer por um material tão minucioso na exemplificação, difícil achar material abordando o assunto como foi abordado aqui. Luiz, como faria para listar, por exemplo, o relacionamento das tabelas? Por exemplo, tenho uma o cadastro de Clientes e dentro desse cadastro, preciso informar o CEP, que é buscado em uma outra tabela para completar informações como endereço, bairro, cidade e uf. Gostaria de exibir essas informações(endereço, bairro, cidade, uf), que são de uma tabela “CEP” por exemplo, dentro da tela de clientes
Olá Thiago,
Creio que você já tenha uma classe TCliente com uma propriedade CEP. Você precisa então, criar uma classe chamada TCEP (poderia ser também TLogradouro, mas vamos seguir o padrão e nomear com o nome da tabela que você informou acima).
Você poderia utilizar algumas formas para retornar o resultado da pesquisa:
Você pode implementar todas estas formas, bastando dar overload na chamada do Read. Porém, para este exemplo, irei demostrar a segunda forma.
Vamos criar a nossa classe TCEP:
Gere os métodos (Ctrl+Shift+C). Seguindo o que foi dito no artigo, chegamos ao seguinte resultado:
A principal diferença entre o que foi visto no artigo e o que está acima é que no read passamos como parâmetro a classe TCep em vez de um dataset.
Agora no seu formulário de cadastro de clientes, você pode instanciar um objeto que será responsável por mostrar os dados do endereço. Por exemplo, digamos que ao sair do campo CEP, você queira mostrar o resultado em labels. Você pode fazer da seguinte forma:
Basicamente é isso! Lembrando que este é um exemplo simples do assunto. Para um projeto real, devemos observar alguns fatores importantes, como por exemplo:
– O banco de dados pode vir a ser trocado por outro no futuro (firebird, MS Sql, MySql, …)?
– As definições das tabelas sofrem muitas alterações (novos campos, alteração de tamanho , etc.)?
– O software a ser desenvolvido é pequeno ou trata-se de um projeto de grande porte?
Isso e muitas outras coisas devem ser levadas em consideração na hora de construir suas classes. Para grandes projetos, deve desde o início procurar deixar o sistema preparado/aberto para as mudanças. Neste exemplo, o projeto é específico para o Firebird, visto que usamos o InterbaseExpress(IBX). O ideal é que utilizemos um conjunto de componentes que possibilite, de forma simples e tranquila, a mudança para um outro banco de dados caso seja necessário no futuro. Neste caso, o recomendado seria a utilização do DbExpress.
Abraços.
Obrigado pela resposta tão esclarecedora Luiz. Hoje estou trabalhando com banco de dados Mysql e componente Zeoslib para conexão com o banco, e o projeto não é tão grande, é para controle de pedidos da empresa onde trabalho. Grande abraço
Poder colocar anexo novamente, este abaixo esta com link quebrado.
Código Fonte: http://dl.dropbox.com/u/478707/BlogDoLuiz-Dao.zip
Infelizmente, esse não tenho mais. Tive algum problema com meu dropbox. Estou aos poucos migrando os arquivos que ainda encontrar.
Opinião.
Não gosto muito da ideia de colocar vários type de classes em uma única Unit.
No caso, do seu exemplo: “TReceber” e “TRecDao” da Unit “uReceber”.
Ou seja, prefiro deixar em arquivo separados.
Já vi units enormes por causa desta flexibilidade.
Gostaria da opinião de vocês.
Eis ai um assunto que pode levar a uma longa discussão.
Contrariamente ao que fiz no post, também tento deixar a unit o mais clean possível, porém se analisarmos a nossa própria linguagem, o Delphi, esta possui units com centenas de classes num único arquivo (veja a Winapi.Windows, por exemplo). Funcionando como Namespaces, agrupam assuntos relacionados num mesmo arquivo.
Além disso, não podemos confundir colocar várias classes num único arquivo com classes com múltiplas responsabilidades. A primeira creio ser mais uma questão de decisão de cada desenvolvedor ou empresa desenvolvedora. A segunda, aí sim, é uma quebra do princípio Single-responsibility (princípio de responsabilidade única) do SOLID.
Abraços.