[Delphi] Design Patterns GoF – Flyweight

Olá, amigos leitores! Feliz 2017!
O primeiro artigo do ano, avançando na nossa temporada sobre Design Patterns, apresenta o próximo padrão de projeto estrutural, chamado Flyweight. Quebrei um pouco a cabeça para imaginar os cenários que este padrão de projeto possa ser adequado, mas, após muita leitura, consegui desvendá-lo! Confira o artigo!

Introdução

Um dos requisitos não-funcionais que sempre terá grande importância em um sistema de informação é o desempenho (ou performance).

Até mesmo já escrevi um artigo sobre as consequências que um software lento pode trazer no cotidiano das pessoas. Na verdade, atualmente, o desempenho corresponde a um diferencial no sucesso de uma aplicação. Usuários esperam que sistemas sejam rápidos e com um consumo de memória satisfatório, ainda mais quando se tratam de aplicativos móveis.

Uma das formas de alcançar boas taxas de desempenho é trabalhar na redução de objetos criados, já que o processo de instanciação pode ser relativamente custoso, principalmente quando o construtor de uma classe inicia vários objetos internos e inicializa variáveis. Além disso, vale lembrar que o processo de construção também envolve gerenciamento de memória, ponteiros e contagens de referência.

O principal objetivo do padrão de projeto Flyweight é melhorar o desempenho de um procedimento através de compartilhamento de objetos com características similares. Em outras palavras, o padrão provê um mecanismo para utilizar objetos já existentes, modificando suas propriedades conforme solicitado, ao invés da necessidade de sempre instanciá-los.

Parece ser o propósito do Singleton…
Sim, a ideia realmente nos remete ao Singleton, porém, há uma diferença: com o Flyweight, é possível trabalhar com vários objetos de uma só vez. A grosso modo, podemos pensar que, basicamente, o Flyweight é uma “lista de Singletons“. Bem basicamente, tá? 🙂

Analogia

Para facilitar a compreensão, imagine o Flyweight como uma “área de cache de objetos”. Quando precisamos utilizar um objeto de uma mesma classe pela segunda vez, mas com propriedades modificadas, em vez de instanciá-lo novamente, buscamos o objeto nesse cache, poupando um novo procedimento de instanciação. É por este motivo que utilizamos o termo “compartilhamento de objetos”.

Considere, por exemplo, uma rotina que desenha 1000 campos de texto em uma tela, cada um com uma das seguintes cores: azul, vermelho ou verde. Uma boa ideia é manter os objetos de desenho de cada cor na memória ao criá-los pela primeira vez. Dessa forma, ao desenhá-los pela segunda vez, basta utilizar os objetos já existentes. No entanto, há um complicador. Cada campo de texto é desenhado em uma nova posição na tela, portanto, não pode ser o mesmo objeto.

A rotina criaria, então, 1000 objetos?
Sim, ao menos que utilizemos o Flyweight para criar apenas 3 objetos!

Como assim? Cada campo de texto deve ser desenhado em uma nova posição na tela, não é?
Exato, mas o Flyweight fornece recursos para alterar algumas propriedades dos objetos compartilhados. Para isso, cada objeto Flyweight possui dois tipos de propriedades:

  • Intrínsecas: propriedades imutáveis, ou seja, que caracterizam o objeto compartilhado. No exemplo acima, é a cor do campo de texto;
  • Extrínsecas: propriedades variáveis que podem receber novos valores a cada acesso. São, portanto, as margens superior e esquerda (posição) do campo de texto.

Com base nessa divisão, podemos afirmar que buscamos o objeto por meio de propriedades intrínsecas e, após encontrá-lo, alteramos as propriedades extrínsecas para adaptá-lo ao novo contexto.

Exemplo de codificação do Flyweight

Pois bem, pessoal, acredito que a aplicabilidade do Flyweight ficará ainda mais sólida com um exemplo prático.

Para este artigo, pensei em uma geração em lote de cartões de agradecimento para leitores de um blog. Teremos um DataSet com leitores cadastrados de diferentes países e, para cada um deles, o sistema deverá gerar um cartão com a bandeira do respectivo país como imagem de fundo, além de uma mensagem personalizada com o nome do leitor.

Já podemos identificar, então, que a imagem da bandeira será a propriedade intrínseca (sempre será a mesma para cada país) e a mensagem com o nome do leitor será a propriedade extrínseca, pois o valor será modificado para cada registro.

Interface Flyweight

A primeira codificação é da Interface Flyweight, uma simples abstração que será implementada posteriormente pelos Concrete Flyweights:

Concrete Flyweights

Os Concrete Flyweights implementam a abstração e correspondem às classes dos objetos compartilhados, ou seja, cada objeto de uma classe Concrete Flyweight será adicionado em uma lista (ou “área de cache”) sob demanda para que sejam “reaproveitados”.

Para evitar a duplicação de código, criaremos uma classe base para os Concrete Flyweights, que terá um objeto da classe TStringList para armazenar o conteúdo da mensagem e um objeto da classe TPNGImage (da biblioteca PNGImage) para trabalhar com arquivos de extensão PNG:

Agora, codificaremos os Concrete Flyweights específicos de cada país herdando da classe base TCartao. Cada objeto dessas classes deverá carregar a imagem da bandeira do país e preencher a mensagem conforme o idioma.

Classe Factory

O último elemento do Flyweight – e o mais importante – é o Factory, responsável, finalmente, por identificar se o objeto existe na lista de objetos compartilhados. Em caso positivo, é retornado para o chamador. Caso contrário, o objeto é criado e adicionado na lista de objetos compartilhados para ser reaproveitado posteriormente. Essa lista também será um objeto da classe TStringList, pois fornece meios para trabalhar com a combinação chave/objeto, conforme veremos no método GetCartao.

Muito bem, pessoal, o nosso padrão de projeto está pronto! O próximo passo é utilizá-lo!

Em ação!

No Client (classe consumidora do padrão), criaremos o Factory e utilizaremos uma variável do tipo TCartao para receber as instâncias dos objetos compartilhados durante as iterações. Olhem só a facilidade:

Para verificar a vantagem de desempenho, fiz questão de também codificar o mesmo procedimento sem utilizar o Flyweight. Nos meus testes de benchmarking, em um DataSet com 500 registros, houve um ganho de 3 segundos. Com 1000 registros, um ganho de 8 segundos.

Pode parecer pouco, mas é importante destacar que os objetos criados neste artigo são bem básicos, com poucos atributos, e a quantidade de registros também é pequena. Em ambientes robustos, com classes compostas por dezenas de atributos e com um processamento envolvendo milhares de registros, a diferença será bem visível!

Na verdade, acho correto afirmar que a eficiência do Flyweight é proporcional à dimensão de processamento. Quanto mais pesado, no sentido de utilização de objetos, mais rápido será o Flyweight em comparação com uma abordagem tradicional.

Qual a diferença de já criar os três objetos antes de iniciar o processamento?
Bom, e se não houver leitores da Espanha? O objeto da classe TCartaoEspanha será criado desnecessariamente, concorda? O Factory do Flyweight possui justamente essa responsabilidade de criar os objetos sob demanda.

Conclusão

Algumas literaturas sobre Design Patterns mencionam que o Flyweight é utilizado em casos bem específicos, o que implica na sua baixa utilização. No entanto, jamais será um padrão de projeto desdenhado.

Um exemplo clássico, comum de se encontrar na internet sobre o Flyweight, é a reutilização de objetos de caracteres em processadores de texto. Ao invés de criar um objeto para cada caractere digitado – no qual carrega informações como fonte, tamanho, cor e formato – utiliza-se o Flyweight para compartilhá-los, reduzindo bastante o consumo de memória.

No link abaixo, disponibilizo o exemplo deste artigo com algumas melhorias. Neste projeto, há dois botões: o primeiro executa a rotina utilizando o Flyweight e o segundo botão executa a rotina de uma forma tradicional, criando e destruindo os objetos dentro do loop. No final de cada processamento, exibo o tempo gasto para comparar as duas formas.

 

Pessoal, em caso de qualquer dúvida, sugestão ou correção no artigo, deixe um comentário! Muitos leitores estão agregando valor nos artigos através dos comentários publicados! Aproveitando, deixo aqui o meu forte agradecimento a todos vocês!

Abração!


 

André Celestino