21 de dezembro de 2011

Design Patterns com Delphi: Template - Parte II

No meu último post, introduzi o conceito do Design Pattern comportamental Template. Em resumo, esse padrão estabelece uma sequência de passos para a execução de uma determinada tarefa, permitindo que sejam criadas heranças que implementem suas próprias versões de um ou mais dos passos, de acordo com a necessidade.

Neste post, eu sugiro uma implementação em Delphi do exemplo envolvendo criação de infraestruturas que usei para apresentar o Template no outro post. Para facilitar o entendimento, reproduzo novamente abaixo o diagrama UML que modela a infraestrutura da transações de negócio usada como exemplo:
Diagrama UML para o padrão Template
Como dá para intuir a partir da simplicidade do diagrama, a codificação também não é complexa. A primeira providência é criar a classe que define o formato geral do algorítimo, os passos que o comporão, em qual ordem esses passos devem ser chamados para completar a tarefa e quais deles poderão ser particularizados em heranças do algorítimo. Esta classe - chamada de Abstract Class - servirá, portanto, de base para as demais implementações do mesmo algorítimo. No nosso exemplo, é a classe TWTransacao que tem essa responsabilidade:
TWTransacao=class
protected
_Erros: TWErros; { Lista de erros da transação }

procedure BeginTrans;
procedure CommitTrans;
procedure RollbackTrans;

function ConsisteDados : boolean;virtual;
function GravaDados : integer;virtual;

public
function ExecutarTransacao : integer;
end;

{ ... }

function TWTransacao.ConsisteDados : boolean;
begin
Result := true;
end;

function TWTransacao.GravaDados : integer;
begin
Result := 0;
end;

function TWTransacao.ExecutarTransacao : integer;
begin
Result := -99;

Try
_Erros.Clear;

{ Inicia uma transação com o banco de dados }
BeginTrans;

{ Se estiver tudo OK com os dados, gravá-los no banco }
if (ConsisteDados) then
Result := GravaDados;

{ Efetiva a transação }
CommitTrans;
Except
{ Se houve algum erro, aborta a transação com o banco de dados}
RollbackTrans;
Result := -100;
end;
end;

Observe que há apenas uma função pública : ExecutarTransacao. As partes do programa que desejarem executar uma transação de negócio precisam ter acesso apenas a ela, não importando quais ou quantos são os passos necessários para executá-la.

Em contraste, tanto os passos do algorítimo que poderão ser estendidos pelas heranças quanto os métodos internos auxiliares foram colocados na área protegida, escondendo os detalhes para códigos externos. Aqui, as funções ConsisteDados e GravaDados poderiam ter sido declaradas como abstratas, o que forçaria as heranças a fornecer-lhes uma implementação. No entanto, não é mandatório que elas tenham essa característica, de modo que o exemplo ilustra como a classe base do Template pode providenciar uma versão padrão dos passos do algorítimo, mesmo que sejam versões bem simples. Elas poderiam, por exemplo, registrar um log da execução.

As funções relativas a transações com o banco de dados (BeginTrans, CommitTrans e RollbackTrans) apenas repassam suas respectivas chamadas diretamente ao banco; por isso, suas implementações foram omitidas no quadro acima.

Já temos a classe base, podemos então codificar as heranças previstas para transações de produtos, pedidos de venda e notas fiscais:
TWTrnProduto = class(TWTransacao)
private
_prod : TWProduto;
protected
function ConsisteDados : boolean;override;
function GravaDados : integer;override;
{ ... }
end;

TWTrnPedidoVenda = class(TWTransacao)
private
_Pedido : TWPedidoVenda;
protected
function ConsisteDados : boolean;override;
function GravaDados : integer;override;
{ ... }
end;

TWTrnNotaFiscal = class(TWTransacao)
private
_Nota : TWNotaFiscal;
protected
function ConsisteDados : boolean;override;
function GravaDados : integer;override;
{ ... }
end;

{ ... }

function TWTrnProduto.ConsisteDados : boolean;
begin
Result := inherited ConsisteDados;

if (TWProduto.ExisteProd (_Prod) ) then
begin
Result := false;
_Erros.Add ('Produto já existe');
end;
end;

function TWTrnProduto.GravaDados : integer;
begin
inherited GravaDados;

_Produto.Insert();
end;

function TWTrnPedidoVenda.ConsisteDados : boolean;
begin
Result := inherited ConsisteDados;

if (TWPedidoVenda.ExistePedido (_Pedido) ) then
begin
Result := false;
_Erros.Add ('Já existe pedido com esse número.');
end;

if (not _Pedido.VerifSaldoProdutos) then
Result := false;

if (not _Pedido.VerifDadosComerciais) then
Result := false;

{ Se detectou algum erro, reporta para a transação }
_Erros.Add(_Pedido.Erros);
end;

function TWTrnPedidoVenda.GravaDados : integer;
begin
inherited GravaDados;

{ Insere o pedido, atualiza saldo dos produtos e faz outros ajustes }
_Pedido.Insert ();
end;
Vemos no quadro anterior que as transações reais são heranças da classe de transação básica e que elas fornecem suas próprias versões de passos do algorítimo base. Com isso, elas garantem que suas próprias regras de negócio sejam respeitadas, enquanto são mantidas a ordem de chamada dos passos e outras regras estipuladas pela classe TWTransacao. A transação de nota fiscal foi omitida no quadro mas a ideia dela é bastante similar à de pedidos de venda.

Para completar o exemplo, falta a classe Client, isto é, aquela parte do código que utilizará o nosso template.
TWTela = class
protected
_Trn : TWTransacao;

public
{ ... }
procedure ExecutarTransacao(); end;

{ ... }

procedure TWTela.ExecutarTransacao();
begin
_Trn := ObtemTransacao ();
_Trn.ExecutarTransacao;
end;
Como mostra o quadro, o código da classe TWTela acaba ficando extremamente simples. Aqui, o maior problema talvez seja decidir o melhor meio de obter uma instância da transação correta. Dependendo das circunstâncias, podemos optar pelo método Factory ou organizar as telas usando o padrão Mediator - o que permitiria cada tela instanciar diretamente a transação adequada.

Nenhum comentário :

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.