Anda di halaman 1dari 26

Design Patterns para o mundo real

Singleton, Factory e DAO


Do que trata o artigo: Este artigo trata da aplicao prtica de Design Patterns em problemas reais que enfrentamos no dia-a-dia. Construiremos um pequeno aplicativo orientado a objetos e ao longo do desenvolvimento implementaremos alguns design patterns para resolver problemas reais do sistema. Para que serve: A aplicao de design patterns pode aumentar a qualidade de seus sistemas, pois se trata de solues testadas e aprovadas para problemas comuns do desenvolvimento de software. Com o uso de Patterns voc pode reduzir acoplamento, melhorar a legibilidade de seu cdigo e aumentar o grau de reuso do mesmo. Em que situao o tema til: Design Patterns so exatamente solues para resolver problemas corriqueiros, ou seja, voc pode us-los a qualquer momento em seus projetos, seja na fase de desenvolvimento ou de manuteno, voc pode se deparar com um problema que j fora analisado e solucionado antes. Resumo do DevMan Neste artigo ns desenvolveremos uma aplicao orientada a objetos e iremos aplicar diversos patterns ao longo deste desenvolvimento. Neste primeiro artigo ns comearemos vendo a aplicao do Singleton para uma classe de conexo com o banco de dados e para o objeto que representa nossa aplicao, alm de vermos o uso de DAOS para abstrair o acesso a dados e Factory para isolar a criao de objetos. Os Design Patterns so padres de design de solues de software que j foram usados e aprovados para solucionar problemas comuns do desenvolvimento. Os Patterns mais famosos so os que foram catalogados no famoso livro conhecido como GOF (Gang Of Four). Apesar de esta literatura conter a descrio de 23 Patterns, no significa que os Design Patterns se limitam a este grupo. O uso destes Patterns, tanto os descritos no GOF quanto outros, contribui para o aumento da qualidade do nosso software, permitindo um menor grau de acoplamento, um maior reuso de cdigo devido ao nvel de abstrao aplicado, uma maior coeso das classes e uma melhor legibilidade do cdigo. Os Design Patterns so independentes de plataforma e tecnologia, sendo possvel aplic-los em qualquer linguagem orientada dos objetos. O conhecimento de orientao a objetos, por sinal, algo imprescindvel para a compreenso dos patterns. Segundo o GOF, os Design Patterns so divididos em trs categorias: - Criacionais - Factory Method, Abstract Factory, Builder, Prototype, Singleton; - Estruturais - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy;

- Comportamentais -Interpreter, Template Method, Chain of Responsability, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Visitor. Hoje existe uma gama enorme de material sobre Design Patterns, porm mais comum encontrar este material para Java e .Net, no muito comum vermos exemplos de aplicaes prticas de Design Patterns em aplicaes Delphi. exatamente isso que vamos tratar aqui, demonstrar de forma prtica, como aplicar Design Patterns em problemas reais que podemos enfrentar diariamente em nossos projetos. Primeiramente ns explicaremos os Patterns que usaremos e depois desenvolveremos uma aplicao simples e ao longo dela iremos implementar os Patterns pertinentes a cada tipo de problema. Aqui usaremos o Singleton para garantirmos apenas uma instncia de nosso objeto Aplicacao e Conexao, o DAO para abstrair o acesso a dados do nosso sistema e a Factory para abstrair e isolar a criaes de nossas DAOs.

Singleton
De acordo com o GOF, o objetivo do Singleton garantir que uma classe possua apenas uma instncia dentro da sua aplicao e que esta instncia possua um ponto de acesso global a todo o sistema. O Singleton precisa garantir que no teremos duas instncias de um determinado objeto em nosso sistema. Como podemos notar na Figura 1, a implementao tradicional do Singleton prev que haja um atributo privado e esttico do tipo da classe, aqui no exemplo chamado de FInstance. Esse atributo onde ficar a nica instncia de nosso objeto.

Figura 1. O padro Singleton Observe tambm que o construtor est privado, isso necessrio para que no seja possvel criar instncias deste objeto em qualquer lugar do sistema. O mtodo GetInstance o nosso ponto de acesso global nossa instncia. Veja na Listagem 1 a implementao em Delphi desta classe. Listagem 1. Implementao padro do Singleton
unit UMySingleton; interface type TMySingleton = class private

class var FInstance:TMySingleton; constructor create(); public class function GetInstance(): TMySingleton; end; implementation { TMySingleton } constructor TMySingleton.create; begin inherited; end; class function TMySingleton. GetInstance: TMySingleton; begin //Verifica se a instncia j foi criada //caso no tenha sido, cria a mesma usando o construtor privado if (not Assigned(FInstance)) then FInstance := create; //retorna a instncia do Singleton result := FInstance; end; end.

DAO - Data Access Objects


Data Access Objects, mais conhecido como DAO, visa separar toda a parte de acesso a dados em classes especficas para esta tarefa. Isso aumenta a coeso do cdigo, j que separamos o modelo e sua regra de negcio dos cdigos SQL para persistncia e recuperao de dados do banco. Normalmente temos uma classe DAO para cada domnio da aplicao. Isso pode parecer ruim, pois aumentaramos consideravelmente o nmero de classes do sistema. Mesmo assim a aplicao deste Pattern interessante, pois mantemos centralizado em uma nica classe todo o cdigo de acesso a dados referente a um determinado modelo. Na aplicao do pattern DAO, os mtodos para as operaes CRUD recebem os objetos a serem persistidos como parmetro, conforme ilustrado na Figura 2 e exemplificado na Listagem 2. Nota: CRUD uma abreviao muito usada no desenvolvimento de software, que representa os mtodos bsicos que a maioria das classes de acesso a dados possuem, ou seja, as operaes de Create, Retrieve, Update e Delete.

Figura 2. Diagrama DAO Listagem 2. Exemplo de DAO


unit UDao; interface type Model = Class //Fields do Modelo End; Dao = Class public function inserir(obj:Model):boolean; function alterar(obj:Model):boolean; function excluir(obj:Model):boolean; End; implementation { Dao } function Dao.alterar(obj:Model): boolean; begin // Cdigo SQL para atualizar o modelo // Abertura de Conexo // Montagem e execuo do SQL end; function Dao.excluir(obj:Model): boolean; begin // Cdigo SQL para atualizar o modelo // Abertura de Conexo // Montagem e execuo do SQL end; function Dao.inserir(obj:Model): boolean; begin // Cdigo SQL para atualizar o modelo // Abertura de Conexo // Montagem e execuo do SQL end; end.

Factory
Este Pattern centraliza a criao de objetos concretos, que possuam uma interface em comum, em uma classe. Esta classe quem decide qual instncia concreta deve ser criada e retornada para o client. No existe uma regra para a condio que servir como base para que a Factory tome a deciso de qual classe criar, porm existem dois modelos que so bastante usados que so por parmetro e por deteco.

Nota: Quando estamos falando em Design Patterns, chamamos de client a classe que consome um determinado servio de outra. Quando por parmetro, a Factory possui um mtodo para criao e retorno do objeto concreto e este mtodo recebe como parmetro alguma informao que possibilite a Factory identificar qual objeto deve ser criado. Quando por deteco, o mtodo da Factory no recebe nenhum parmetro, porm decide qual objeto criar baseado na deteco de alguma informao ou configurao do sistema, como por exemplo, atravs de uma configurao em um arquivo INI ou XML. Observe a arquitetura deste Pattern na Figura 3, para criao de objetos que representam produtos. A Listagem 3 mostra o cdigo Delphi deste Pattern. Onde: Client quem vai solicitar uma instncia do AbstractProduct; AbstractProduct - Abstrao do objeto que ser instanciado e retornado pela Factory. Pode ser uma classe base ou uma interface; ConcreteProductA e B - Duas implementaes diferentes de AbstractProduct. Sero os retornos da Factory.

Figura 3. Diagrama Factory

Listagem 3. Implementao do Factory


unit ExemploFactory; interface type AbstractProduct = class //Mtodos padres da classe base end; ConcreteProductA = class(AbstractProduct) //Implementaes da classe concreta A end; ConcreteProductB = class(AbstractProduct) //Implementaes da classe concreta B end; ProductFactory = class public class function getProduct (tipo:String):AbstractProduct; end; implementation { ProductFactory } class function ProductFactory.getProduct (tipo:String): AbstractProduct; begin if(tipo = A)then result := ConcreteProductA.Create(); if(tipo = B)then result := ConcreteProductB.Create(); end; end.

Na Listagem 3 ns usamos a verificao por parmetro para decidir qual objeto concreto retornar. Com isso abstramos de nosso client a implementao concreta de nosso produto. O nosso client passa a trabalhar apenas com nosso produto abstrato, assim ns diminumos o acoplamento e deixamos nosso projeto mais flexvel a mudanas.

Construindo uma aplicao prtica


Nossa aplicao comear bem simples, apenas com um cadastro de usurios, de clientes e uma tela de login. Apesar dessa simplicidade, j implementaremos os trs Patterns citados anteriormente: Singleton, DAO e Factory. Conforme podemos notar no diagrama da Figura 4, teremos uma DAO para cada

um de nossos modelos. Estas DAOs implementaro a interface IDAO e usaro os servios de conexo que, no nosso caso, estaro encapsulados na classe TConexaoBD, que por sua vez ter toda a configurao para conexo com o banco, alm de uma instncia de um TSQLConnection que ser fornecida s DAOs. Na parte de baixo do diagrama temos nossos modelos herdando de um modelo base. Este modelo base ter um identificador e um State, que o que usaremos para saber qual mtodo da persistncia deveremos executar (Insert, Update, Delete). Teremos uma Factory para construir nossas DAOs. Observe que dessa maneira os formulrios desconhecem completamente a implementao da DAO que lhes atender, eles conhecem apenas a DaoFactory e ela que vai decidir qual DAO retornar de acordo com o modelo passado como parmetro. Temos ainda uma classe encapsulando aspectos da aplicao, como por exemplo, a verso, a data e hora de login e o usurio logado.

Construindo os modelos
Na Listagem 4 temos a implementao de um tipo enumerado chamado TStateDomain, que servir para identificar qual o estado do nosso modelo e assim decidirmos qual mtodo de persistncia usar. Alm deste tipo enumerado temos nosso modelo base, ele bem simples, a princpio temos apenas um ID que ser o identificador do nosso objeto e um StateDomain para nos indicar o estado do objeto. Na Listagem 5 temos a implementao de nossos modelos Cliente e Usurio, ambos herdam de nosso modelo base TBaseDomain. Note que como eles herdam de TBaseDomain, no precisamos definir as propriedades Id e State. So estes domnios que

sero passados para as DAOs para serem persistidos. Note que o cdigo extremamente simples, apenas define a classe, seus atributos e mtodos get / set.

Implementando a classe de conexo


Quando desenvolvemos de forma puramente RAD, jogando os componentes de acesso a dados na tela, ligando-os diretamente a componentes DataAware, ns simplesmente configuramos a conexo em tempo de design e associamos a ela os DataSets necessrios tambm em tempo de design. Mas como estamos construindo uma aplicao OO, persistindo modelos de objetos ao invs de DataPackets do DataSetProvider / ClientDataSet, ento o ideal que criemos uma classe para encapsular a parte de conexo com o banco de dados. Listagem 4. Implementao do modelo base
unit UBaseDomain; interface type TStateDomain = (sdNew,sdEdit,sdBrowse); TBaseDomain = class private FId: integer; FState: TStateDomain; procedure Setid(const Value: integer); procedure SetState(const Value: TStateDomain); published property Id:integer read Fid write Setid; property State:TStateDomain read FState write SetState; end; implementation { TBaseDomain } procedure TBaseDomain.SetId (const Value: integer); begin Fid := Value; end; procedure TBaseDomain.SetState (const Value: TStateDomain); begin FState := Value; end; end.

Listagem 5. Implementao dos modelos Cliente e Usuario


unit UCliente; interface uses UBaseDomain,UUsuario; type TCliente = class(TBaseDomain) private FBairro: String; FIdade: Integer; FNome: String; FUsuarioCadastro: TUsuario; FDiaCadastro: TDate; FHoraCadastro: TTime; procedure SetBairro(const Value: String); procedure SetIdade(const Value: Integer); procedure SetNome(const Value: String); procedure SetUsuarioCadastro (const Value: TUsuario); procedure SetDiaCadastro(const Value: TDate); procedure SetHoraCadastro(const Value: TTime); published property Nome:String read FNome write SetNome; property Idade:Integer read FIdade write SetIdade; property Bairro:String read FBairro write SetBairro; property UsuarioCadastro:TUsuario read FUsuarioCadastro write SetUsuarioCadastro; property DiaCadastro:TDate read FDiaCadastro write SetDiaCadastro; property HoraCadastro:TTime read FHoraCadastro write SetHoraCadastro; end; implementation { TCliente } procedure TCliente.SetBairro(const Value: String); begin FBairro := Value; end; procedure TCliente.SetDiaCadastro (const Value: TDate); begin FDiaCadastro := Value; end; procedure TCliente.SetHoraCadastro (const Value: TTime); begin

FHoraCadastro := Value; end; procedure TCliente.SetIdade(const Value: Integer); begin FIdade := Value; end; procedure TCliente.SetNome(const Value: String); begin FNome := Value; end; procedure TCliente.SetUsuarioCadastro (const Value: TUsuario); begin FUsuarioCadastro := Value; end; end. unit UUsuario; interface uses UBaseDomain; type TUsuario = class(TBaseDomain) private FSenha: String; FLogin: String; FNome: String; procedure SetLogin(const Value: String); procedure SetNome(const Value: String); procedure SetSenha(const Value: String); published property Login:String read FLogin write SetLogin; property Senha:String read FSenha write SetSenha; property Nome:String read FNome write SetNome; end; implementation { TUsuario } procedure TUsuario.SetLogin(const Value: String); begin FLogin := Value; end; procedure TUsuario.SetNome(const Value: String); begin FNome := Value; end;

procedure TUsuario.SetSenha(const Value: String); begin FSenha := Value; end; end.

Nesta classe aplicaremos o Singleton para evitar que, por algum erro de implementao, tenhamos duas instncias de conexo em nossa aplicao (Listagem 6).
Listagem 6. Implementao da classe de conexo unit UConexaoBD; interface uses SqlExpr; type TConexaoBD = class private FConexaoBD: TSQLConnection; FDataBaseFile: String; FPassword: String; FUserName: String; //Varivel esttica para armazenar a //nica instncia de nossa classe class var FInstance:TConexaoBD; procedure SetConexaoBD(const Value: TSQLConnection); procedure SetDataBaseFile(const Value: String); procedure SetPassword(const Value: String); procedure SetUserName(const Value: String); procedure LoadDefaultConfig(); //Construtor privado para que possamos criar //o objeto apenas de dentro da prpria classe constructor create(); published property ConexaoBD:TSQLConnection read FConexaoBD write SetConexaoBD; property DataBaseFile:String read FDataBaseFile write SetDataBaseFile; property UserName:String read FUserName write SetUserName; property Password:String read FPassword write SetPassword; public //Mtodo esttico para retornar nosso //objeto de conexo class function GetInstance():TConexaoBD; procedure LoadConfig(); end;

implementation { TConexaoBD }

constructor TConexaoBD.create; begin inherited; //Criamos nossa connection que ser //fornecida para as DAOs ConexaoBD := TSQLConnection.Create(nil); //Carregamos as configuraes default da conexo LoadDefaultConfig; //Carregamos as configuraes de conexo LoadConfig; end; class function TConexaoBD.GetInstance: TConexaoBD; begin if(not Assigned(FInstance))then FInstance := create; result := FInstance; end; procedure TConexaoBD.LoadConfig; begin //Verificamos se o connection est criado // para evitarmos um AccessViolation if(Assigned(ConexaoBD))then begin if(not ConexaoBD.Connected)then begin //Definimos o nome da conexo para carregar as //informaes padres de conexo ConexaoBD.ConnectionName := FBConnection; ConexaoBD.DriverName := Firebird; //LoadParamsFromIniFile carregar os //parmetros padres de //conexo com base no ConnectionName ConexaoBD.LoadParamsFromIniFile(); //Definimos nossas configuraes de usurio, //senha e caminho do BD ConexaoBD.Params[ConexaoBD.Params. IndexOfName(Password)] := Password=+Password; ConexaoBD.Params[ConexaoBD.Params. IndexOfName(User_Name)] := User_Name=+UserName; ConexaoBD.Params[ConexaoBD.Params. IndexOfName(Database)] := Database=+DataBaseFile; ConexaoBD.LoginPrompt := false; //Abrimos a conexo ConexaoBD.Connected := true; end; end; end; procedure TConexaoBD.LoadDefaultConfig; begin

DataBaseFile := localhost:D:\Artigos\ ClubeDelphi\Singleton\bd\ExDP.FDB; UserName := SYSDBA; Password := masterkey; end; procedure TConexaoBD.SetConexaoBD (const Value: TSQLConnection); begin FConexaoBD := Value; end; procedure TConexaoBD.SetDataBaseFile (const Value: String); begin FDataBaseFile := Value; end; procedure TConexaoBD.SetPassword (const Value: String); begin FPassword := Value; end; procedure TConexaoBD.SetUserName (const Value: String); begin FUserName := Value; end; end.

Implementando as DAOs
Vamos fazer mais uma analogia ao desenvolvimento RAD. Quando desenvolvemos de forma RAD, normalmente colocamos os DataSets no formulrio / DM e j inserimos ali mesmo o cdigo SQL para manipulao dos dados. Dessa forma fica tudo junto, misturado, impossibilitando o reuso. Se voc faz um cadastro de clientes dessa forma, no conseguir reutilizar o cdigo nele presente. Imagine que em outra ocasio voc precise fazer a importao de clientes de outra base de dados, fatalmente ter que reescrever o cdigo de insero de clientes. A implementao da DAO ajuda neste aspecto, pois como o cdigo SQL ficar isolado em uma classe, conseguimos reutilizar o mesmo em diferentes ocasies, alm de facilitar a manuteno. Na Listagem 7 temos a definio da nossa interface IDAO, que possui todo o comportamento comum a todas as DAOs da nossa aplicao. A implementao de uma interface fundamental para que possamos implementar nossa Factory, como veremos mais adiante. Listagem 7. Definio da interface IDAO
unit UIDao;

interface uses UBaseDomain, Generics.Collections; type IDao = interface procedure Insert(pDomain:TBaseDomain); procedure Update(pDomain:TBaseDomain); procedure Delete(pDomain:TBaseDomain); //Retorna um modelo com base em um ID function GetById(pIdDomain:Integer): TBaseDomain; //Retorna um modelo com base em campo e valor Function GetByField(Field:String; Value:String):TBaseDomain; //Retorna uma lista com todos os modelos function GetAll():TList<TBaseDomain>; End; implementation end.

Na Listagem 8 podemos ver a implementao dos mtodos de persistncia de cliente. Note que em todos os casos ns seguimos uma ordem onde primeiramente criamos a query, recuperamos a conexo com o BD, montamos a SQL, passamos os parmetros e enfim executamos a SQL. Aps isso, mudamos o estado do objeto para sdBrowse, pois a operao fora realizada. Note que necessrio realizarmos um Cast do parmetro que do tipo TBaseDomain para acessarmos os valores de nosso modelo cliente. Observe tambm que usamos a nossa TConexaoBD para recuperar a conexo com o BD e associar nossa query. No mtodo inserir temos uma particularidade que a persistncia do objeto associado, que o usurio que cadastrou o cliente em questo, ento pegamos o ID do mesmo para persistir no BD.

Listagem 8. Mtodos de persistncia da DaoCliente


unit UClienteDao; interface uses UCliente, UIDao, UBaseDomain,Generics. Collections,SQLExpr,UConexaoBD,SysUtils; type TClienteDao public procedure procedure procedure

= Class(TInterfacedObject,IDao) Insert(pCliente:TBaseDomain); Update(pCliente:TBaseDomain); Delete(pCliente:TBaseDomain);

Function GetById(pIdDomain:Integer): TBaseDomain; Function GetByField(Field:String;Value: String):TBaseDomain; function GetAll: TList<TBaseDomain>; End; implementation uses UDaoFactory,UUsuario; { TClienteDao } procedure TClienteDao.Insert(pCliente: TBaseDomain); var Qry:TSQLQuery; begin Qry := TSQLQuery.Create(nil); Qry.SQLConnection := TConexaoBD. GetInstance.ConexaoBD; Qry.SQL.Add(INSERT INTO CLIENTE(NOME,IDADE, BAIRRO,ID_USUARIO) VALUES); Qry.SQL.Add((:pNOME,:pIDADE,:pBAIRRO, :pID_USUARIO)); Qry.ParamByName(pNOME).AsString := TCliente(pCliente).Nome; Qry.ParamByName(pIDADE).AsInteger := TCliente(pCliente).Idade; Qry.ParamByName(pBAIRRO).AsString := TCliente(pCliente).Bairro; Qry.ParamByName(pID_USUARIO).AsInteger := TCliente(pCliente).UsuarioCadastro.Id; Qry.ExecSQL(); pCliente.State := sdBrowse; end; procedure TClienteDao.Update(pCliente: TBaseDomain); var Qry:TSQLQuery; begin Qry := TSQLQuery.Create(nil); Qry.SQLConnection := TConexaoBD. GetInstance.ConexaoBD; Qry.SQL.Add(UPDATE CLIENTE SET BAIRRO =: pBAIRRO,IDADE = :pIDADE,NOME = :pNOME); Qry.SQL.Add( WHERE ID = :pID); Qry.ParamByName(pNOME).AsString := TCliente(pCliente).Nome; Qry.ParamByName(pIDADE).AsInteger := TCliente(pCliente).Idade; Qry.ParamByName(pBAIRRO).AsString := TCliente(pCliente).Bairro; Qry.ParamByName(pID).AsInteger := TCliente(pCliente).Id; Qry.ExecSQL(); pCliente.State := sdBrowse; end;

procedure TClienteDao.Delete(pCliente: TBaseDomain); var Qry:TSQLQuery; begin Qry := TSQLQuery.Create(nil); Qry.SQLConnection := TConexaoBD. GetInstance.ConexaoBD; Qry.SQL.Add(DELETE FROM CLIENTE ); Qry.SQL.Add( WHERE ID = :pID); Qry.ParamByName(pID).AsInteger := TCliente(pCliente).Id; Qry.ExecSQL(); pCliente.State := sdBrowse; end;

Na Listagem 9 temos a implementao de nossos mtodos de recuperao de dados, onde seguimos o mesmo padro, criando a query, recuperando a conexo, montando a SQL, passando os parmetros, executando a SQL e por fim populamos um objeto Cliente para ser retornado. Observe como populamos a propriedade UsuarioCadastro, neste caso ns solicitamos DaoFactory uma nova DAO de usurio e ento executamos o mtodo GetByID passando o ID recuperado em nossa consulta. O GetALL tem uma particularidade que o retorno de uma lista com todos os clientes cadastrados. Para isso fazemos a consulta no BD e ento percorremos todo o resultado da query, instanciando um cliente para cada registro da consulta e adicionando este cliente nossa lista para ser retornada pelo mtodo. Em todos os casos ns reutilizamos o mtodo GetById da UsuarioDao, algo que provavelmente no seria possvel se no tivssemos isolado a parte de acesso a dados em classes DAOs. Listagem 9. Mtodos Get da DaoCliente
function TClienteDao.GetAll: TList<TBaseDomain>; var Qry:TSQLQuery; Lista:TList<TBaseDomain>; Cliente:TCliente; begin Qry := TSQLQuery.Create(nil); Qry.SQLConnection := TConexaoBD. GetInstance.ConexaoBD; Qry.SQL.Add(SELECT ID,NOME,IDADE,BAIRRO, ID_USUARIO FROM CLIENTE ); Qry.Open(); lista := TList<TBaseDomain>.Create(); while not(Qry.Eof) do begin Cliente := TCliente.Create; Cliente.Id := Qry.FieldByName(ID). AsInteger; Cliente.Nome := Qry.FieldByName(NOME). AsString; Cliente.Idade := Qry.FieldByName(IDADE). AsInteger;

Cliente.Bairro := Qry.FieldByName(BAIRRO). AsString; Cliente.UsuarioCadastro := TUsuario(TDaoFactory. GetDao(TUsuario.Create()).GetById(Qry. FieldByName(ID_USUARIO).AsInteger)); Lista.Add(Cliente); Qry.Next; end; Result := lista; end; function TClienteDao.GetByField(Field, Value: String): TBaseDomain; var Cliente:TCliente; Qry:TSQLQuery; begin Cliente := TCliente.Create; Qry := TSQLQuery.Create(nil); Qry.SQLConnection := TConexaoBD. GetInstance.ConexaoBD; Qry.SQL.Add(SELECT SELECT ID,NOME,IDADE, BAIRRO,ID_USUARIO FROM CLIENTE ); Qry.SQL.Add( WHERE + Field + = + quotedStr(Value)); Qry.Open(); Cliente.Id := Qry.FieldByName(ID).AsInteger; Cliente.Nome := Qry.FieldByName(NOME).AsString; Cliente.Idade := Qry.FieldByName(IDADE).AsInteger; Cliente.Bairro := Qry.FieldByName(BAIRRO).AsString; Cliente.UsuarioCadastro := TUsuario(TDaoFactory.GetDao (TUsuario.Create()).GetById(Qry. FieldByName(ID_USUARIO).AsInteger)); result := Cliente; end; function TClienteDao.GetById(pIdDomain: Integer): TBaseDomain; var Qry:TSQLQuery; Cliente:TCliente; begin Qry := TSQLQuery.Create(nil); Qry.SQLConnection := TConexaoBD. GetInstance.ConexaoBD; Qry.SQL.Add(SELECT ID,NOME,IDADE,BAIRRO, ID_USUARIO FROM CLIENTE ); Qry.SQL.Add( WHERE ID = :pID); Qry.ParamByName(pID).AsInteger := pIdDomain; Qry.Open(); Cliente := TCliente.Create; Cliente.Id := Qry.FieldByName(ID).AsInteger; Cliente.Nome := Qry.FieldByName(NOME).AsString; Cliente.Idade := Qry.FieldByName(IDADE).AsInteger; Cliente.Bairro := Qry.FieldByName(BAIRRO).AsString; Cliente.UsuarioCadastro :=

TUsuario(TDaoFactory.GetDao (TUsuario.Create()).GetById(Qry.FieldByName (ID_USUARIO).AsInteger)); Result := Cliente; end; end.

Enquanto o mtodo GetALL retorna a lista de um determinado objeto usando Generics, os mtodos GetByID e GetByField retornam apenas um objeto dependendo do parmetro passado. O primeiro retorna o objeto de acordo com o ID informado, j o segundo permite uma consulta por qualquer atributo do objeto, basta informar o nome do atributo e seu valor. Nota: O mtodo GetAll utiliza um novo recurso disponvel desde o Delphi 2009 em Win32 chamado Generics. Com Generics podemos abstrair um tipo de dado e defini-lo em tempo de execuo. Isso flexibiliza o cdigo e evita o uso excessivo de Type Casting. Na Listagem 10 temos a implementao de nossa Factory. No mtodo GetDao ns recebemos um modelo como parmetro e verificamos com o operador IS qual o tipo do modelo para ento criarmos a DAO correta. Fechamos aqui a implementao de nossos modelos e nossas DAOs, ns no falaremos da DAOUsuario pois segue o mesmo padro da DAOCliente. Listagem 10. DaoFactory
unit UDaoFactory; interface uses UBaseDomain, UIDao, UCliente, UUsuario, UClienteDao, UUsuarioDao; type TDaoFactory = class class function GetDao(pDomain: TBaseDomain):IDao; end; implementation { TDaoFactory<T> } class function TDaoFactory.GetDao(pDomain: TBaseDomain): IDao; var ConcreteDao:IDao; begin if(pDomain is TCliente)then ConcreteDao := TCLienteDao.create() else if(pDomain is TUsuario)then ConcreteDao := TUsuarioDao.create();

result := ConcreteDao; end; end.

Implementando o Singleton em Taplicacao


Na Listagem 11 temos a implementao de nosso Singleton em TAplicacao. A ideia que este objeto encapsule aspectos referentes a nossa aplicao, tendo inicialmente dados como verso, usurio logado e data e hora de login. Observe o padro de implementao do Singleton, onde temos um atributo esttico FInstance para armazenar nossa instncia, o construtor privado e um mtodo esttico e pblico para retornar a instncia, semelhante ao que temos em nossa classe de conexo. Listagem 11. TAplicacao
unit UAplicacao; interface uses UUsuario; type TAplicacao = class private FVersao: String; FUsuarioLogado: TUsuario; FDataHoraLogin: TDateTime; class var FInstance:TAplicacao; procedure SetDataHoraLogin (const Value: TDateTime); procedure SetVersao (const Value: String); constructor create(); procedure SetUsuarioLogado (const Value: TUsuario); published property Versao:String read FVersao write SetVersao; property UsuarioLogado:TUsuario read FUsuarioLogado write SetUsuarioLogado; property DataHoraLogin:TDateTime read FDataHoraLogin write SetDataHoraLogin; public class function GetInstance():TAplicacao; end; implementation { TAplicacao } constructor TAplicacao.create; begin

inherited; end; class function TAplicacao.GetInstance: TAplicacao; begin if not assigned(FInstance)then FInstance := create(); result := FInstance; end; procedure TAplicacao.SetDataHoraLogin (const Value: TDateTime); begin FDataHoraLogin := Value; end; procedure TAplicacao.SetUsuarioLogado (const Value: TUsuario); begin FUsuarioLogado := Value; end; procedure TAplicacao.SetVersao (const Value: String); begin FVersao := Value; end; end.

Construindo os formulrios
J falamos de todas as nossas classes de domnio, persistncia e infraestrutura, agora vamos implementao de nossos formulrios, a comear pelo formulrio de login (Figura 5).

Figura 5. Tela de Login Na Listagem 12 temosa validao de usurios. Primeiro ns solicitamos uma DAO para usurio. Depois chamamos o mtodo GetByField passando o campo que desejamos filtrar, que neste caso o LOGIN e o respectivo valor digitado no edit login. Este mtodo nos retornar o usurio que possui este login, ento na linha de baixo ns verificamos se a instncia de usurio no est criada ou se a senha diferente, isto porque se o login for invlido, o usurio ser igual nil. Se uma destas condies for verdadeira, ns exibimos uma mensagem ao usurio, caso contrrio seguimos a execuo do programa criando o formulrio principal e atribuindo o usurio retornado da DAO ao UsuarioLogado de nossa aplicao. Em nosso formulrio de cadastro de clientes, temos uma ComboBox que exibir todos os clientes cadastrados e medida que um cliente for selecionado, ir carregar seus dados no formulrio permitindo a alterao ou excluso do mesmo. A Figura 6 mostra uma sugesto de tela. Neste formulrio ns temos declarado na sesso private trs atributos, conforme mostrado a seguir: private { Private declarations } Cliente:TCliente; Dao:IDao; ListaClientes:TList<TCliente>;

Figura 6. Cadastro de clientes Listagem 12. Cdigo do click do boto Login


procedure TFrmLogin.btnLoginClick(Sender: TObject); var dao:IDao; usuario:TUsuario; begin dao := TDaoFactory.GetDao (TUsuario.Create); usuario := TUsuario(dao.GetByField (LOGIN,edtLogin.Text)); if (not assigned(usuario))or(not (usuario.Senha = edtSenha.Text)) then begin ShowMessage(Usurio e/ou Senha invlidos.); Abort; end; Application.CreateForm (TFrmPrincipal,FrmPrincipal); TAplicacao.GetInstance. UsuarioLogado := usuario; FrmPrincipal.Show; self.Hide; end;

Na Listagem 13 ns limpamos o contedo de nossa ComboBox e ento chamamos o mtodo GetAll de nossa DAO que retornar uma lista com todos os clientes cadastrados para nossa ListaClientes, depois disso ns percorremos esta lista adicionando os itens em nosso ComboBox. No esquea de declarar a procedure LoadListAtCombo na seo private do formulrio. A seguir vemos o cdigo do FormShow onde criamos a DAO de cliente e

em seguida carregamos a lista de clientes cadastrados em nossa ComboBox: procedure TFrmCadCliente.FormShow(Sender: TObject); begin Dao := TDaoFactory.GetDao(TCliente.Create); LoadListAtCombo(); end; Listagem 13. Carga dos clientes na ComboBox
procedure TFrmCadCliente.LoadListAtCombo; var c:TCliente; begin CbbClientes.Items.Clear; ListaClientes := TList<TCliente> (Dao.GetAll()); for c in ListaClientes do begin CbbClientes.AddItem(c.nome,c); end; end;

Na Listagem 14 temos os mtodos responsveis por passar os valores do modelo selecionado para os controles da tela (ModelToView) e vice-versa, da tela para o modelo a ser persistido (ViewToModel). Listagem 14. Mtodos para trocar dados entre tela e domnio
procedure TFrmCadCliente.ModelToView (pCliente: TCliente); begin edtNome.Text := pCliente.Nome; EdtIdade.Text := IntToStr (pCliente.Idade); EdtBairro.Text := pCliente.Bairro; EdtUsuarioCadastro.Text := pCliente.UsuarioCadastro.Nome; end; procedure TFrmCadCliente.ViewToModel (pCliente: TCliente); begin pCliente.Nome := edtNome.Text; pCliente.Idade := StrToInt (EdtIdade.Text); pCliente.Bairro := EdtBairro.Text; pCliente.DiaCadastro := DateOf(now); pCliente.HoraCadastro := TimeOf(now); end;

Na Listagem 15 temos o cdigo dos botes Novo, Editar e Excluir que seguem o mesmo padro, apenas alteram o estado do modelo e o estado dos botes da tela. No caso

do novo temos ainda a instanciao de um novo cliente, o que no necessrio no caso do editar, pois o cliente j est carregado. No caso da excluso ns simplesmente executamos o Delete da DAO passando o cliente como parmetro, aps isso executamos o LoadListAtCombo apenas para atualizarmos a nossa lista. Listagem 15. Cdigo dos botes Novo, Editar e Excluir
procedure TFrmCadCliente.BtnNovoClick (Sender: TObject); begin Cliente := TCliente.Create; Cliente.State := sdNew; BtnGravar.Enabled := true; BtnCancelar.Enabled := True; BtnExcluir.Enabled := false; BtnEditar.Enabled := false; BtnNovo.Enabled := false; ClearControls; end; procedure TFrmCadCliente.BtnEditarClick (Sender: TObject); begin Cliente.State := sdEdit; BtnGravar.Enabled := true; BtnCancelar.Enabled := True; BtnExcluir.Enabled := false; BtnEditar.Enabled := false; BtnNovo.Enabled := false; end; procedure TFrmCadCliente.BtnExcluirClick (Sender: TObject); begin Dao.Delete(Cliente); LoadListAtCombo(); ShowMessage(Cliente excludo com sucesso.); end;

Ao selecionarmos um cliente na ComboBox, exibiremos seus dados na tela, para isso temos o cdigo a seguir implementado no evento OnSelect da ComboBox: procedure TFrmCadCliente.CbbClientesSelect(Sender: TObject); begin Cliente := ListaClientes[CbbClientes.ItemIndex]; ModelToView(Cliente); end; Na Listagem 16 temos o mtodo de gravao, nele executamos nosso mtodo para passar os valores da tela para o nosso modelo e ento verificamos o estado do mesmo para ento executarmos o Insert ou Update. No caso de uma incluso, ns alimentamos ainda o

UsuarioCadastro do cliente, usando o Singleton de TAplicao para pegarmos a instncia de TAplicacao e em seguida o usurio logado, que alimentamos na tela de login. Listagem 16. Cdigo do boto Gravar
procedure TFrmCadCliente. BtnGravarClick(Sender: TObject); begin ViewToModel(Cliente); if(Cliente.State = sdNew)then begin Cliente.UsuarioCadastro := TAplicacao.GetInstance. UsuarioLogado; Dao.Insert(cliente); end else if(Cliente.State = sdEdit)then Dao.Update(cliente); BtnGravar.Enabled := false; BtnCancelar.Enabled := false; BtnExcluir.Enabled := true; BtnEditar.Enabled := true; BtnNovo.Enabled := true; ShowMessage(Cliente gravado com sucesso.); end;

Concluso
Com isso fechamos nossa pequena aplicao de cadastro de clientes, porm com a aplicao de trs patterns bem comuns, o Singleton, Factory e DAO, para nos ajudar a resolver necessidades comuns do nosso dia-a-dia. Mesmo neste simples exemplo podemos notar alguns grandes benefcios do uso destes Patterns, a comear pelo Singleton, que nos d a garantia que s teremos uma instncia de Conexo e Aplicao em nosso sistema. O uso de DAO isolou todo o cdigo de acesso a dados nos permitindo desacoplar mais nossa aplicao, aumentando sua coeso e tambm a reutilizar mais cdigo. Finalizando com a Factory, que centralizou a criao das DAOs em um nico lugar e que fez as classes consumidoras das DAOs, incluindo os formulrios, desconhecerem completamente a implementao das DAOs concretas, fazendo-as trabalhar apenas com a interface IDAO.

Anda mungkin juga menyukai