10 de dezembro de 2009

Design Patterns com Delphi : FlyWeight - parte 2

Há alguns, falei aqui no blog sobre o conceito e terminiologia associados ao Design Pattern estrutural FlyWeight. Faltou mostrar como organizar e implementar as classes usando Delphi. Recordando o conceito, esse design pattern tem por objetivo minimizar o uso de memória (e outros recursos) quando a criação de uma grande quantidade de instâncias de classe é necessária para uma determinada aplicação.

Para facilitar a compreensão, reproduzo novamente abaixo o diagrama UML que exemplifica o uso do FlyWeight:
Diagrama UML para FlyWeight

Baseado no diagrama, teremos que declarar a classe TWFonte que atua como uma interface, isto é, sua característica principal é introduzir o comportamento (as funções) que permite compartilhar dados intrínsecos da classe e aplicar temporariamente a esses dados outras informações externas (os dados extrínsecos) particulares a cada contexto.

É bastante comum que esse tipo de classe seja abstrata pura, isto é, que não possua variáveis internamente - esta característica é reservada às versões concretas da classe - mas isso não é uma obrigação. No exemplo do diagrama, há informações comuns aos tipos de Fonte que fazem sentido ser colocados nessa classe:
TWFonte = class
private
{ Nome do Fonte }
_Nome : String;
{ Lista de imagens disponíveis para o Fonte }
_Imagens : TList;

constructor Create(ANome : String); overload;
public
constructor Create;reintroduce; overload;
procedure Desenha(AContext: TWContext; AStyle: TWStyle) = 0;
end;
{ ... }
constructor TWFonte.Create(ANome : String);
begin
_Nome := ANome;
{ ... }
end;

constructor TWFonte.Create;
begin
Raise Exception.Create('Classe deve ser construída a partir da Factory');
end;

Veja que há dois construtores com o nome de Create, um privado e outro público. A intenção por trás disso é forçar o uso da classe de Factory quando for necessário obter instâncias do TWFonte já que é o Factory quem garante que apenas uma instância de cada herança concreta existe e, em última análise, garante o sucesso do compartilhamento dos dados intrínsecos de cada fonte. Para maiores detalhes sobre essa técnica de esconder um construtor, veja nesse endereço a seção Hiding a Constructor.

Como dito no parágrafo anterior, a classe Factory para o pattern Flyweight funciona de forma ligeiramente diferente daquela estabelecida pelo pattern Factory Method. Aqui será preciso ter uma instância da classe Factory (provavelmente um Singleton) para gerenciar as instâncias criadas:
TWFonteFactory = class
private
_Fontes : TStringList;
{ ... }
public
function GetFonte (ANome: String) : TWFonte;
end;
{ ... }
function TWFonteFactory.GetFonte (ANome: String) : TWFonte;
var idx : integer;
begin
Result := nil;
idx := _Fontes.IndexOf(ANome);
if (idx >= 0) then
Result := _Fontes.Objects[idx] As TWFonte
else begin
if ANome = 'Arial' then
Result := TWArial.Create
else
if ANome = 'TimesNewRoman' then
Result := TWTimesNewRoman.Create;
end;
end;

Talvez a diferença mais importante nesse Factory é que ele mantem internamente as instâncias criadas e, quando solicitado, retornam um ponteiro para uma instância para uso externo. Então, embora não apareça no trecho de código acima, o destructor da classe TWFonteFactory deve destruir todas as instâncias de TWFonte criadas e armazenadas por ela. Com isso, deve-se tomar cuidado para não usar uma instância do TWFonte após o destrutor da Factory ter sido chamado.

As últimas classes relevantes para o FlyWeight são as que implementam concretamente a interface de compartilhamento. No exemplo, as ConcreteFlyWeight são a TWArial e a TWTimesNewRoman, que são codificadas como heranças simples:
TWArial = class(TWFonte)
public
procedure Desenha(AContext: TWContext; AStyle: TWStyle);
end;
{ ... }
procedure TWArial.Desenha(AContext: TWContext; AStyle: TWStyle);
var lImagem : TImagem;
begin
lImagem := GeraImagem (_Imagens, AStyle, AContext.Texto);
DesenhaImagem (AContext, lImagem);
end;

A destacar nesse trecho é a forma como a função Desenha faz uso das propriedades intrínsecas e dos parâmetros para obter o resultado desejado. Os parâmetros são propriedades extrínsecas - isto é, de fora da classe - e, portanto, não são compartilhados. A função interna GeraImagem cria uma imagem para cada caractere do texto e concatena todas elas para gerar a imagem correspondente ao texto informado no Contexto. A imagem de cada caractere é escolhida na lista interna de acordo com o estilo e tamanho requisitado pelos parâmetros. Para finalizar, a função interna DesenhaImagem toma a imagem gerada e a desenha usando as informações estipulada pelo contexto - handle do canvas, localização, etc..

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.