19 de janeiro de 2010

Design Patterns com Delphi : Proxy - parte 2

Neste post, mostro como transformar em código Delphi as classes envolvidas para construir uma solução usando o pattern Proxy. Para facilitar a clareza, reproduzo abaixo o diagrama UML publicado no post anterior que descreve simplificadamente a solução. Para mais informações sobre o conceito por trás do padrão Proxy, veja o post anterior.
Diagrama UML para Proxy

Para começar, devemos estabelecer o Subject como uma classe abstrata, isto é, ele servirá apenas para demarcar quais são as operações e propriedades disponíveis no nosso Proxy. No diagrama acima, esta classe é o TWSigner:
type
TWSigner = class
{ ... }
public
class function GetTWSigner (AProxy: boolean) : TWSigner;
function Assinar (AXml: String): String;virtual;abstract;
end;

Com isso assentado, podemos então partir para a implementação das duas classes que satisfazem essa interface, a saber o TWSignerProxy e o TWRemoteSigner. O TWRemoteSigner é quem efetivamente se comunica com o serviço remoto de assinatura enquanto o TWSignerProxy é um Proxy para a classe de comunicação remota e que postergará a conexão até que esta seja de fato necessária:
type
TWSignerProxy = class(TWSigner)
protected
_Signer: TWRemoteSigner;
{ ... }
public
function Assinar (AXml: String): String;override;
end;

TWRemoteSigner = class(TWSigner)
protected
_Connected : boolean;
{ ... }
public
function Conectar (AHost: String; APort: integer): boolean;
function Desconectar : boolean;

function Assinar (AXml: String): String;override;
end;

{ ... }
implementation
{ ... }

function TWSignerProxy.Assinar (AXml: String): String;
begin
{ Cria a instância para comunicação remota, se ainda não foi criada }
if _Signer = Nil then
_Signer := TWRemoteSigner.Create;

{ Faz a conexão, se ainda não fez }
if not _Signer._Connected then
_Signer.Conectar (ObterHost, ObterPort);

{ Repassa o comando de assinatura para a classe de comunicação remota }
Result := _Signer.Assinar (AXml);
end;

Veja no código acima que a conexão remota só é estabelecida pelo Proxy quando estritamente necessária. A funcionalidade de um Proxy poderia incluir ainda outros tratamentos com o intuito de minimizar esperas, reduzir uso de memória e outros recursos ou ainda fazer validação de credenciais de um usuário antes de lhe dar acesso a determinada informação. Por exemplo, poderia criar um cache local, armazenando internamente certos dados e usá-los quando necessário no futuro para evitar a criação de novas instâncias de classes, conexões remotas ou a carga de um arquivo.

Os padrões de projeto não são soluções isoladas, estanques. Quero dizer com isso que é bastante frequente envolver dois (ou até mais) dos padrões para se projetar a solução para um único problema computacional. No exemplo desenvolvido neste post, por exemplo, é interessante determinar em tempo de execução qual a classe que deve ser instanciada para suprir a referência de TWSigner na nossa classe client. Este é claramente o cenário apropriado para se usar um Factory Method:
class function TWSigner.GetTWSigner (AProxy: boolean) : TWSigner;
begin
if AProxy then
Result := TWSignerProxy.Create
else
Result := TWRemoteSigner.Create;
end;

Com essa Factory, a classe Client pode decidir se quer ou não usar o Proxy. Para o caso, por exemplo, do programa Cliente estar em execução na mesma máquina que o Servidor de assinaturas pode ser mais interessante instanciar diretamente a classe de assinatura. Isso é possível porque ambas as classes respeitam a mesma interface.

Para usar isso tudo, a classe TWBusinessObj - que é nosso Client para esse exemplo - deve obter a instância necessária de TWSigner através da Factory. Com isso, ela desconhece os detalhes de implementação desse signer; para ela, basta que a assinatura de que ela precisa seja feita corretamente.
TWBusinessObj = class
protected
_Signer: TWSigner;

public
procedure DoOperacao;
end;
{ ... }

procedure TWSBusinessObj.DoOperacao;
var lAssim : String;
begin
{ ... }
{ Cria a instância para assinatura. Não dá pra saber de antemão se é a versão Proxy ou a versão remota ... }
if _Signer = Nil then
_Signer := TWSigner.GetTWSigner (_UsaProxy);

lAssin := _Signer.Assinar (AXml);

{ ... }
end;


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.