25 de fevereiro de 2010

Design Patterns com Delphi : Chain Of Responsability - parte II

Do ponto de vista da complexidade da hierarquia de classes, a implementação de uma solução para a Cadeia de Responsabilidade não apresenta grandes desafios. As classes do tipo Handler e Concrete Handler mantêm entre si uma relação simples de herança - incluí no post anterior descrições para os tipos de classe participantes; lá também citei um exemplo de uso para o Pattern. Reproduzo abaixo o mesmo diagrama daquele post para mostrar as relações entre as classes do exemplo proposto:
Diagrama UML para Chain Of Responsability
Então, para implementar a solução, devemos primeiro criar a classe Handler como sendo abstrata para introduzir as funcionalidades que estarão disponíveis na Cadeia de Responsabilidade. Depois, criamos as heranças dessa classe (os Concrete Handlers) para implementar as regras específicas para cada nível de responsabilidade, conforme ditado pelas regras de negócio.
No exemplo, as classes concretas dizem respeito à necessidade de aprovação de documentos e cada uma delas aplica suas próprias regras de acordo com o poder de decisão necessário para efetuar uma aprovação.
type
TWAprovacao = class
{ ... }
protected
_Sucessor: TWAprovacao;
{ ... }

procedure SetSucessor (aSucessor: TWAprovacao);
function PrecisaAprovacao (aDoc: TWDocumento) : boolean;virtual;abstract;
procedure RequisitaAprovacao (aDoc: TWDocumento);virtual;abstract;
end;

TWAprovacaoBasica = class(TWAprovacao)
{ ... }
function PrecisaAprovacao (aDoc: TWDocumento) : boolean;override;
procedure RequisitaAprovacao (aDoc: TWDocumento);override;
end;

TWAprovacaoGerente = class(TWAprovacao)
{ ... }
function PrecisaAprovacao (aDoc: TWDocumento) : boolean;override;
procedure RequisitaAprovacao (aDoc: TWDocumento);override;
end;
Veja que na classe base TWAprovacao há um membro (variável) chamado _Sucessor. Esse membro representa o próximo nível a ser consultado na hierarquia de responsabilidades. Assim, quando as regras de uma classe não permitem que ela tome uma decisão, ela repassa para seu "sucessor" a responsabilidade de decidir - daí o nome de Cadeia de Responsabilidade para o Pattern. As funções que verificam a aprovação em cada classe, então, devem levar o sucessor em conta:
function TWAprovacaoBasica.PrecisaAprovacao (aDoc: TWDocumento) : boolean;
begin;
Result := true;
{ Regra básica para determinar se o documento precisará de uma aprovação }
if (aDoc.Valor > 1500) then begin
if (_Sucessor <> Nil) then
Result := _Sucessor.PrecisaAprovacao (aDoc);
end
else
Result := false;
end;

procedure TWAprovacaoBasica.RequisitaAprovacao (aDoc: TWDocumento);
begin;
if PrecisaAutorizacao then begin
{ Executa ações necessárias para obter aprovação básica. Exemplos: envio de email, gravação no BD, etc}
end;
end;

function TWAprovacaoGerente.PrecisaAprovacao (aDoc: TWDocumento) : boolean;
begin;
Result := true;
{ Regra aplicada a gerentes para determinar se o documento precisará de uma aprovação }
if (aDoc.Valor > 8000) then begin
if (_Sucessor <> Nil) then
Result := _Sucessor.PrecisaAprovacao (aDoc);
end
else
Result := false;
end;

procedure TWAprovacaoGerente.RequisitaAprovacao (aDoc: TWDocumento);
begin;
if PrecisaAutorizacao then begin
{ Executa ações necessárias para obter aprovação para Gerentes. Exemplos: envio de email, gravação no BD ou outra específica. }
end;
end;
A chave para o funcionamento da solução está na forma como o encadeamento é montado. Neste ponto, invariavelmente teremos que utilizar algum dos padrões dedicados à flexibilizar a criação de instâncias de classes, tal como o Factory Method:
function GetAprovacaoFromFactory (AUsuario: TWUsuario): TWAprovacao;
var lUltSucessor, lAux : TWAprovacao;
begin
{ A aprovação básica sempre existe e é o ponto de entrada das aprovações }
Result := TWAprovacaoBasica.Create;
lUltSucessor := Result;

if AUsuario.TipoCargo >= tcGerente then begin
lAux := TWAprovacaoGerente.Create;
lUltSucessor.SetSucessor (lAux);
lUltSucessor := lAux;
end;

{ Cria sucessivamente a hierarquia de aprovações ... }
if AUsuario.TipoCargo >= tcDiretor then begin
lAux := TWAprovacaoDiretor.Create;
lUltSucessor.SetSucessor (lAux);
lUltSucessor := lAux;
end;
{ ... }
end;
Observe que a montagem da cadeia de responsabilidades começa pelo nível hierárquico mais baixo - que está sempre presente - e vai crescendo conforme aumenta o cargo do Usuário que está solicitando a aprovação.

A função que obtem a instância da hierarquia de aprovações pode, então, ser utilizada dentro da classe do tipo Client para realizar as operações necessárias. No diagrama do início do post, o tipo Client é encarnado pela classe TWBusinessObj. Se for preciso modificar as regras de negócio ou os relacionamentos existentes na cadeia, a classe Client fará uso das novas regras sem que seja necessário modificá-la - graças ao grau baixo de acoplamento existente.

As regras apresentadas neste exemplo são deliberadamente simples para reforçar o conceito por trás do Design Pattern. Obviamente, as regras de aprovação podem ser muita mais complexas, envolvendo outros elementos e cenários existentes numa aplicação real, assim como a montagem da hierarquia de aprovação pode gerar uma cadeia muito mais extensa.

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.