Design Patterns Para o Mundo Real 1

26
Design Patterns para o mundo real Singleton, Factory e DAO Do que trata o artigo: Este artigo trata da aplicação prática 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 aplicação de design patterns pode aumentar a qualidade de seus sistemas, pois se trata de soluções testadas e aprovadas para problemas comuns do desenvolvimento de software. Com o uso de Patterns você pode reduzir acoplamento, melhorar a legibilidade de seu código e aumentar o grau de reuso do mesmo. Em que situação o tema é útil: Design Patterns são exatamente soluções para resolver problemas corriqueiros, ou seja, você pode usá-los a qualquer momento em seus projetos, seja na fase de desenvolvimento ou de manutenção, você pode se deparar com um problema que já fora analisado e solucionado antes. Resumo do DevMan Neste artigo nós desenvolveremos uma aplicação orientada a objetos e iremos aplicar diversos patterns ao longo deste desenvolvimento. Neste primeiro artigo nós começaremos vendo a aplicação do Singleton para uma classe de conexão com o banco de dados e para o objeto que representa nossa aplicação, além de vermos o uso de DAO´S para abstrair o acesso a dados e Factory para isolar a criação de objetos. Os Design Patterns são padrões de design de soluções de software que já foram usados e aprovados para solucionar problemas comuns do desenvolvimento. Os Patterns mais famosos são os que foram catalogados no famoso livro conhecido como GOF (Gang Of Four). Apesar de esta literatura conter a descrição de 23 Patterns, não 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 código devido ao nível de abstração aplicado, uma maior coesão das classes e uma melhor legibilidade do código. Os Design Patterns são independentes de plataforma e tecnologia, sendo possível aplicá-los em qualquer linguagem orientada dos objetos. O conhecimento de orientação a objetos, por sinal, é algo imprescindível para a compreensão dos patterns. Segundo o GOF, os Design Patterns são divididos em três categorias: - Criacionais - Factory Method, Abstract Factory, Builder, Prototype, Singleton; - Estruturais - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy;

Transcript of Design Patterns Para o Mundo Real 1

Page 1: Design Patterns Para o Mundo Real 1

Design Patterns para o mundo real Singleton, Factory e DAO Do que trata o artigo: Este artigo trata da aplicação prática 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 aplicação de design patterns pode aumentar a qualidade de seus sistemas, pois se trata de soluções testadas e aprovadas para problemas comuns do desenvolvimento de software. Com o uso de Patterns você pode reduzir acoplamento, melhorar a legibilidade de seu código e aumentar o grau de reuso do mesmo. Em que situação o tema é útil: Design Patterns são exatamente soluções para resolver problemas corriqueiros, ou seja, você pode usá-los a qualquer momento em seus projetos, seja na fase de desenvolvimento ou de manutenção, você pode se deparar com um problema que já fora analisado e solucionado antes. Resumo do DevMan Neste artigo nós desenvolveremos uma aplicação orientada a objetos e iremos aplicar diversos patterns ao longo deste desenvolvimento. Neste primeiro artigo nós começaremos vendo a aplicação do Singleton para uma classe de conexão com o banco de dados e para o objeto que representa nossa aplicação, além de vermos o uso de DAO´S para abstrair o acesso a dados e Factory para isolar a criação de objetos.

Os Design Patterns são padrões de design de soluções de software que já foram usados e aprovados para solucionar problemas comuns do desenvolvimento. Os Patterns mais famosos são os que foram catalogados no famoso livro conhecido como GOF (Gang Of Four). Apesar de esta literatura conter a descrição de 23 Patterns, não 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 código devido ao nível de abstração aplicado, uma maior coesão das classes e uma melhor legibilidade do código. Os Design Patterns são independentes de plataforma e tecnologia, sendo possível aplicá-los em qualquer linguagem orientada dos objetos. O conhecimento de orientação a objetos, por sinal, é algo imprescindível para a compreensão dos patterns.

Segundo o GOF, os Design Patterns são divididos em três categorias: - Criacionais - Factory Method, Abstract Factory, Builder, Prototype, Singleton; - Estruturais - Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy;

Page 2: Design Patterns Para o Mundo Real 1

- 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, porém é mais comum encontrar este material para Java e .Net, não é muito comum vermos exemplos de aplicações práticas de Design Patterns em aplicações Delphi. É exatamente isso que vamos tratar aqui, demonstrar de forma prática, como aplicar Design Patterns em problemas reais que podemos enfrentar diariamente em nossos projetos.

Primeiramente nós explicaremos os Patterns que usaremos e depois desenvolveremos uma aplicação simples e ao longo dela iremos implementar os Patterns pertinentes a cada tipo de problema. Aqui usaremos o Singleton para garantirmos apenas uma instância 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 criações de nossas DAO´s. Singleton De acordo com o GOF, o objetivo do Singleton é garantir que uma classe possua apenas uma instância dentro da sua aplicação e que esta instância possua um ponto de acesso global a todo o sistema. O Singleton precisa garantir que não teremos duas instâncias de um determinado objeto em nosso sistema. Como podemos notar na Figura 1, a implementação tradicional do Singleton prevê que haja um atributo privado e estático do tipo da classe, aqui no exemplo chamado de FInstance. Esse atributo é onde ficará a única instância de nosso objeto.

Figura 1. O padrão Singleton Observe também que o construtor está privado, isso é necessário para que não seja possível criar instâncias deste objeto em qualquer lugar do sistema. O método GetInstance é o nosso ponto de acesso global à nossa instância. Veja na Listagem 1 a implementação em Delphi desta classe. Listagem 1. Implementação padrão do Singleton unit UMySingleton; interface type TMySingleton = class private

Page 3: Design Patterns Para o Mundo Real 1

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 instância já foi criada //caso não tenha sido, cria a mesma usando o constr utor privado if (not Assigned(FInstance)) then FInstance := create; //retorna a instância 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 específicas para esta tarefa. Isso aumenta a coesão do código, já que separamos o modelo e sua regra de negócio dos códigos SQL para persistência e recuperação de dados do banco. Normalmente temos uma classe DAO para cada domínio da aplicação. Isso pode parecer ruim, pois aumentaríamos consideravelmente o número de classes do sistema. Mesmo assim a aplicação deste Pattern é interessante, pois mantemos centralizado em uma única classe todo o código de acesso a dados referente a um determinado modelo. Na aplicação do pattern DAO, os métodos para as operações CRUD recebem os objetos a serem persistidos como parâmetro, conforme ilustrado na Figura 2 e exemplificado na Listagem 2. Nota: CRUD é uma abreviação muito usada no desenvolvimento de software, que representa os métodos básicos que a maioria das classes de acesso a dados possuem, ou seja, as operações de Create, Retrieve, Update e Delete.

Page 4: Design Patterns Para o Mundo Real 1

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 // Código SQL para atualizar o modelo // Abertura de Conexão // Montagem e execução do SQL end; function Dao.excluir(obj:Model): boolean; begin // Código SQL para atualizar o modelo // Abertura de Conexão // Montagem e execução do SQL end; function Dao.inserir(obj:Model): boolean; begin // Código SQL para atualizar o modelo // Abertura de Conexão // Montagem e execução do SQL end; end.

Factory Este Pattern centraliza a criação de objetos concretos, que possuam uma interface em comum, em uma classe. Esta classe é quem decide qual instância concreta deve ser criada e retornada para o client. Não existe uma regra para a condição que servirá como base para que a Factory tome a decisão de qual classe criar, porém existem dois modelos que são bastante usados que são por parâmetro e por detecção.

Page 5: Design Patterns Para o Mundo Real 1

Nota: Quando estamos falando em Design Patterns, chamamos de client a classe que consome um determinado serviço de outra. Quando é por parâmetro, a Factory possui um método para criação e retorno do objeto concreto e este método recebe como parâmetro alguma informação que possibilite a Factory identificar qual objeto deve ser criado. Quando é por detecção, o método da Factory não recebe nenhum parâmetro, porém decide qual objeto criar baseado na detecção de alguma informação ou configuração do sistema, como por exemplo, através de uma configuração em um arquivo INI ou XML. Observe a arquitetura deste Pattern na Figura 3, para criação de objetos que representam produtos. A Listagem 3 mostra o código Delphi deste Pattern. Onde: Client - É quem vai solicitar uma instância do AbstractProduct; AbstractProduct - Abstração do objeto que será instanciado e retornado pela Factory. Pode ser uma classe base ou uma interface; ConcreteProductA e B - Duas implementações diferentes de AbstractProduct. Serão os retornos da Factory.

Figura 3. Diagrama Factory

Page 6: Design Patterns Para o Mundo Real 1

Listagem 3. Implementação do Factory

unit ExemploFactory; interface type AbstractProduct = class //Métodos padrões da classe base end; ConcreteProductA = class(AbstractProduct) //Implementações da classe concreta A end; ConcreteProductB = class(AbstractProduct) //Implementações 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 nós usamos a verificação por parâmetro para decidir qual objeto concreto retornar. Com isso abstraímos de nosso client a implementação concreta de nosso produto. O nosso client passa a trabalhar apenas com nosso produto abstrato, assim nós diminuímos o acoplamento e deixamos nosso projeto mais flexível a mudanças. Construindo uma aplicação prática Nossa aplicação começará bem simples, apenas com um cadastro de usuários, de clientes e uma tela de login. Apesar dessa simplicidade, já implementaremos os três Patterns citados anteriormente: Singleton, DAO e Factory. Conforme podemos notar no diagrama da Figura 4, teremos uma DAO para cada

Page 7: Design Patterns Para o Mundo Real 1

um de nossos modelos. Estas DAO´s implementarão a interface IDAO e usarão os serviços de conexão que, no nosso caso, estarão encapsulados na classe TConexaoBD, que por sua vez terá toda a configuração para conexão com o banco, além de uma instância de um TSQLConnection que será fornecida às DAO´s. 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 método da persistência deveremos executar (Insert, Update, Delete). Teremos uma Factory para construir nossas DAO´s. Observe que dessa maneira os formulários desconhecem completamente a implementação 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 parâmetro. Temos ainda uma classe encapsulando aspectos da aplicação, como por exemplo, a versão, a data e hora de login e o usuário logado.

Page 8: Design Patterns Para o Mundo Real 1

Construindo os modelos Na Listagem 4 temos a implementação de um tipo enumerado chamado TStateDomain, que servirá para identificar qual o estado do nosso modelo e assim decidirmos qual método de persistência usar. Além deste tipo enumerado temos nosso modelo base, ele é bem simples, a princípio 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 implementação de nossos modelos Cliente e Usuário, ambos herdam de nosso modelo base TBaseDomain. Note que como eles herdam de TBaseDomain, não precisamos definir as propriedades Id e State. São estes domínios que

Page 9: Design Patterns Para o Mundo Real 1

serão passados para as DAO´s para serem persistidos. Note que o código é extremamente simples, apenas define a classe, seus atributos e métodos get / set. Implementando a classe de conexão Quando desenvolvemos de forma puramente RAD, jogando os componentes de acesso a dados na tela, ligando-os diretamente a componentes DataAware, nós simplesmente configuramos a conexão em tempo de design e associamos a ela os DataSets necessários também em tempo de design. Mas como estamos construindo uma aplicação OO, persistindo modelos de objetos ao invés de DataPackets do DataSetProvider / ClientDataSet, então o ideal é que criemos uma classe para encapsular a parte de conexão com o banco de dados. Listagem 4. Implementação 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.

Page 10: Design Patterns Para o Mundo Real 1

Listagem 5. Implementação 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

Page 11: Design Patterns Para o Mundo Real 1

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 SetLogi n; property Senha:String read FSenha write SetSenh a; 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;

Page 12: Design Patterns Para o Mundo Real 1

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

Nesta classe aplicaremos o Singleton para evitar que, por algum erro de implementação, tenhamos duas instâncias de conexão em nossa aplicação (Listagem 6). Listagem 6. Implementação da classe de conexão unit UConexaoBD; interface uses SqlExpr; type TConexaoBD = class private FConexaoBD: TSQLConnection; FDataBaseFile: String; FPassword: String; FUserName: String; //Variável estática para armazenar a //única instância 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 própria 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 //Método estático para retornar nosso //objeto de conexão class function GetInstance():TConexaoBD; procedure LoadConfig(); end; implementation { TConexaoBD }

Page 13: Design Patterns Para o Mundo Real 1

constructor TConexaoBD.create; begin inherited; //Criamos nossa connection que será //fornecida para as DAOs ConexaoBD := TSQLConnection.Create(nil); //Carregamos as configurações default da conexão LoadDefaultConfig; //Carregamos as configurações de conexão 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 conexão para carregar a s //informações padrões de conexão ConexaoBD.ConnectionName := FBConnection; ConexaoBD.DriverName := Firebird; //LoadParamsFromIniFile carregará os //parâmetros padrões de //conexão com base no ConnectionName ConexaoBD.LoadParamsFromIniFile(); //Definimos nossas configurações de usuário, //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 conexão ConexaoBD.Connected := true; end; end; end; procedure TConexaoBD.LoadDefaultConfig; begin

Page 14: Design Patterns Para o Mundo Real 1

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 DAO’s Vamos fazer mais uma analogia ao desenvolvimento RAD. Quando desenvolvemos de forma RAD, normalmente colocamos os DataSets no formulário / DM e já inserimos ali mesmo o código SQL para manipulação dos dados. Dessa forma fica tudo junto, misturado, impossibilitando o reuso. Se você faz um cadastro de clientes dessa forma, não conseguirá reutilizar o código nele presente. Imagine que em outra ocasião você precise fazer a importação de clientes de outra base de dados, fatalmente terá que reescrever o código de inserção de clientes. A implementação da DAO ajuda neste aspecto, pois como o código SQL ficará isolado em uma classe, conseguimos reutilizar o mesmo em diferentes ocasiões, além de facilitar a manutenção. Na Listagem 7 temos a definição da nossa interface IDAO, que possui todo o comportamento comum a todas as DAO´s da nossa aplicação. A implementação de uma interface é fundamental para que possamos implementar nossa Factory, como veremos mais adiante. Listagem 7. Definição da interface IDAO unit UIDao;

Page 15: Design Patterns Para o Mundo Real 1

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 implementação dos métodos de persistência de cliente. Note que em todos os casos nós seguimos uma ordem onde primeiramente criamos a query, recuperamos a conexão com o BD, montamos a SQL, passamos os parâmetros e enfim executamos a SQL. Após isso, mudamos o estado do objeto para sdBrowse, pois a operação fora realizada. Note que é necessário realizarmos um Cast do parâmetro que é do tipo TBaseDomain para acessarmos os valores de nosso modelo cliente. Observe também que usamos a nossa TConexaoBD para recuperar a conexão com o BD e associar à nossa query. No método inserir temos uma particularidade que é a persistência do objeto associado, que é o usuário que cadastrou o cliente em questão, então pegamos o ID do mesmo para persistir no BD. Listagem 8. Métodos de persistência da DaoCliente unit UClienteDao; interface uses UCliente, UIDao, UBaseDomain,Generics. Collections,SQLExpr,UConexaoBD,SysUtils; type TClienteDao = Class(TInterfacedObject,IDao) public procedure Insert(pCliente:TBaseDomain); procedure Update(pCliente:TBaseDomain); procedure Delete(pCliente:TBaseDomain);

Page 16: Design Patterns Para o Mundo Real 1

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;

Page 17: Design Patterns Para o Mundo Real 1

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 implementação de nossos métodos de recuperação de dados, onde seguimos o mesmo padrão, criando a query, recuperando a conexão, montando a SQL, passando os parâmetros, executando a SQL e por fim populamos um objeto Cliente para ser retornado. Observe como populamos a propriedade UsuarioCadastro, neste caso nós solicitamos à DaoFactory uma nova DAO de usuário e então executamos o método 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 então percorremos todo o resultado da query, instanciando um cliente para cada registro da consulta e adicionando este cliente à nossa lista para ser retornada pelo método. Em todos os casos nós reutilizamos o método GetById da UsuarioDao, algo que provavelmente não seria possível se não tivéssemos isolado a parte de acesso a dados em classes DAO´s. Listagem 9. Métodos 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;

Page 18: Design Patterns Para o Mundo Real 1

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).AsStrin g; 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).AsStrin g; Cliente.UsuarioCadastro :=

Page 19: Design Patterns Para o Mundo Real 1

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

Enquanto o método GetALL retorna a lista de um determinado objeto usando Generics, os métodos GetByID e GetByField retornam apenas um objeto dependendo do parâmetro 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 método GetAll utiliza um novo recurso disponível desde o Delphi 2009 em Win32 chamado Generics. Com Generics podemos abstrair um tipo de dado e defini-lo em tempo de execução. Isso flexibiliza o código e evita o uso excessivo de Type Casting. Na Listagem 10 temos a implementação de nossa Factory. No método GetDao nós recebemos um modelo como parâmetro e verificamos com o operador IS qual o tipo do modelo para então criarmos a DAO correta. Fechamos aqui a implementação de nossos modelos e nossas DAO´s, nós não falaremos da DAOUsuario pois segue o mesmo padrão 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();

Page 20: Design Patterns Para o Mundo Real 1

result := ConcreteDao; end; end.

Implementando o Singleton em Taplicacao Na Listagem 11 temos a implementação de nosso Singleton em TAplicacao. A ideia é que este objeto encapsule aspectos referentes a nossa aplicação, tendo inicialmente dados como versão, usuário logado e data e hora de login. Observe o padrão de implementação do Singleton, onde temos um atributo estático FInstance para armazenar nossa instância, o construtor privado e um método estático e público para retornar a instância, semelhante ao que temos em nossa classe de conexão. 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

Page 21: Design Patterns Para o Mundo Real 1

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 formulários Já falamos de todas as nossas classes de domínio, persistência e infraestrutura, agora vamos à implementação de nossos formulários, a começar pelo formulário de login (Figura 5).

Page 22: Design Patterns Para o Mundo Real 1

Figura 5. Tela de Login Na Listagem 12 temosa validação de usuários. Primeiro nós solicitamos uma DAO

para usuário. Depois chamamos o método GetByField passando o campo que desejamos filtrar, que neste caso é o LOGIN e o respectivo valor digitado no edit login. Este método nos retornará o usuário que possui este login, então na linha de baixo nós verificamos se a instância de usuário não está criada ou se a senha é diferente, isto porque se o login for inválido, o usuário será igual nil. Se uma destas condições for verdadeira, nós exibimos uma mensagem ao usuário, caso contrário seguimos a execução do programa criando o formulário principal e atribuindo o usuário retornado da DAO ao UsuarioLogado de nossa aplicação. Em nosso formulário 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 formulário permitindo a alteração ou exclusão do mesmo. A Figura 6 mostra uma sugestão de tela. Neste formulário nós temos declarado na sessão private três atributos, conforme mostrado a seguir: private { Private declarations } Cliente:TCliente; Dao:IDao;

ListaClientes:TList<TCliente>;

Page 23: Design Patterns Para o Mundo Real 1

Figura 6. Cadastro de clientes Listagem 12. Código do click do botão 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(Usuário e/ou Senha inválidos.); Abort; end; Application.CreateForm (TFrmPrincipal,FrmPrincipal); TAplicacao.GetInstance. UsuarioLogado := usuario; FrmPrincipal.Show; self.Hide; end;

Na Listagem 13 nós limpamos o conteúdo de nossa ComboBox e então chamamos o método GetAll de nossa DAO que retornará uma lista com todos os clientes cadastrados para nossa ListaClientes, depois disso nós percorremos esta lista adicionando os itens em nosso ComboBox. Não esqueça de declarar a procedure LoadListAtCombo na seção private do formulário. A seguir vemos o código do FormShow onde criamos a DAO de cliente e

Page 24: Design Patterns Para o Mundo Real 1

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 métodos responsáveis 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. Métodos para trocar dados entre tela e domínio 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 código dos botões Novo, Editar e Excluir que seguem o mesmo padrão, apenas alteram o estado do modelo e o estado dos botões da tela. No caso

Page 25: Design Patterns Para o Mundo Real 1

do novo temos ainda a instanciação de um novo cliente, o que não é necessário no caso do editar, pois o cliente já está carregado. No caso da exclusão nós simplesmente executamos o Delete da DAO passando o cliente como parâmetro, após isso executamos o LoadListAtCombo apenas para atualizarmos a nossa lista. Listagem 15. Código dos botões 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 excluído com sucesso.); end;

Ao selecionarmos um cliente na ComboBox, exibiremos seus dados na tela, para isso temos o código 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 método de gravação, nele executamos nosso método para passar os valores da tela para o nosso modelo e então verificamos o estado do mesmo para então executarmos o Insert ou Update. No caso de uma inclusão, nós alimentamos ainda o

Page 26: Design Patterns Para o Mundo Real 1

UsuarioCadastro do cliente, usando o Singleton de TAplicação para pegarmos a instância de TAplicacao e em seguida o usuário logado, que alimentamos na tela de login. Listagem 16. Código do botão 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;

Conclusão Com isso fechamos nossa pequena aplicação de cadastro de clientes, porém com a aplicação de três 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 benefícios do uso destes Patterns, a começar pelo Singleton, que nos dá a garantia que só teremos uma instância de Conexão e Aplicação em nosso sistema. O uso de DAO isolou todo o código de acesso a dados nos permitindo desacoplar mais nossa aplicação, aumentando sua coesão e também a reutilizar mais código. Finalizando com a Factory, que centralizou a criação das DAO´s em um único lugar e que fez as classes consumidoras das DAO´s, incluindo os formulários, desconhecerem completamente a implementação das DAO´s concretas, fazendo-as trabalhar apenas com a interface IDAO.