E ai galera, tudo certo?

Retomando nossos estudos sobre design patterns, hoje vamos abordar o pattern Flyweight!

Flyweight

O padrão Flyweight é utilizado quando temos vários objetos que devem ser tratados em memória, onde muitos deles possuem informações repetidas. Considerando que memória é um recurso limitado, podemos segregar estas informações repetidas em um objeto adicional imutável e comparável.

Resumindo o parágrafo acima de uma forma mais simples e clara, usamos um objeto compartilhando ele o máximo possível, reduzindo a alocação de memória.

Um exemplo clássico do uso deste pattern é uma estrutura de dados para representação gráfica de caracteres em um editor de texto. O ideal é termos uma representação gráfica para cada caractere digitado, com sua fonte, tamanho, etc., mas alocar cada um , iria consumir muita memória. Em vez disso, para cada caractere, temos uma referência para um objeto que representa graficamente o caractere, sendo este objeto denominado um Flyweight, compartilhado entre todas as instâncias / ocorrências deste caractere.

De acordo com o livro Design Patterns do Gang of Four, o pattern Flyweight foi criado e muito explorado por Paul Calder e Mark Linton em meados de 1990, para gerenciar informações de forma efetiva em um editor de textos do tipo WYSIWYG.

Outra aplicação deste pattern é no desenvolvimento de jogos. Quem aqui já jogou Age of Empires II (Ok, entreguei minha idade agora…rs)?

Tela do jogo Age of Empires II, onde temos muitos, mas muitos objetos em atividade na tela, mas com representações gráficas comuns, repetidas.

Cada representação gráfica distinta é instanciada apenas uma vez, e cada objeto, soldado, cavalo, elefante, etc. faz referência a essa instância, de acordo com seu tipo de representação.

Um detalhe de atenção é que, para garantir o compartilhamento de forma segura, entre clientes e threads, objetos Flyweight devem ser imutáveis.

Vamos analisar os detalhes do pattern diretamente na ficha resumo e ver alguns exemplos utilizando C#.

Ficha Resumo

  • Nome: Flyweight;
  • Objetivo / intenção: Quando precisamos otimizar o uso de memória com estruturas de dados onde temos muitos dados repetidos;
  • Motivação: Quando temos estruturas de dados grandes, com uma grande quantidade de dados repetidos, alocar todos em memória pode ser extremamente custoso, dado que memória é um recurso limitado. Este padrão surgiu justamente para otimizar o uso de memória, consistindo no compartilhamento de uma instância entre vários objetos diferentes.
  • Aplicabilidade: O padrão Flyweight é bastante utilizado quando queremos evitar a alocação de instâncias repetidas em memória, dado que este é um recurso limitado. Um exemplo de utilização é em editores de texto com fontes, onde temos uma única instância de representação gráfica para cada caractere, onde cada caractere digitado no documento apenas aponta para a instância correspondente;
  • Estrutura: No desenho abaixo, temos um exemplo UML de aplicação do pattern para desenhar círculos em diferentes localizações. Descrevendo cada parte temos:
Representação UML do pattern Flyweight
  • ShapeFactory: Possui um HashMap interno de círculos, tendo como chave de identificação a cor do círculo. Sempre que um novo círculo for requisitado com uma cor específica, ele irá verificar se já não construiu algum daquela cor. Se sim, irá fornecer uma referência para a o círculo já existente, caso contrário, irá criar um novo, armazenar no HashMap e fornecer a referência dele.
  • FlyweightPatternDemo: Classe de demonstração, apenas para exemplificar o cliente que está utilizando a Factory, solicitando os círculos e suas cores.
  • Shape e Circle: Interface para definir uma forma gráfica e implementação da mesma como círculo, que são utilizadas pelo ShapeFactory e fornecidas ao cliente FlyweightPatternDemo.
  • Consequências: O Flyweight resolve problemas com alocação de memória onde temos estruturas de dados grandes e com dados repetidos, garantindo uma redução na alocação de recursos limitados e otimizando a performance de aplicações, mas temos que tomar cuidado com a questão de concorrência, onde temos a criação de objetos em múltiplas threads. Se a lista de valores for limitada e tivermos conhecimento prévio disto, podemos instanciar antecipadamente e obter os objetos de um contêiner centralizado. Já se tivermos o instanciamento em múltiplas threads, temos algumas opções como tornar a instanciação para single threaded, ou através de semáforos, garantindo uma instância por valor, ou permitir a criação de mais de um valor do mesmo tipo, filtrando / eliminando isto ao obter os valores, mas somente se seu objeto for capaz de ser comparável com outros do seu tipo.
  • Implementações: Abaixo temos um exemplo de código em C# e Java para o pattern:
  • Usos conhecidos: Muito utilizado na construção de aplicações com grandes estruturas de dados ou objetos, como editores de texto e jogos, onde otimizamos o uso de memória e até processamento trabalhando com referências para instâncias únicas de objetos que podem se repetir por N vezes durante o fluxo de execução do programa.
  • Padrões relacionados: Bridge, Proxy;

Concluindo

Este foi o padrão Flyweight, com o qual podemos otimizar o uso de recursos como memória na utilização de grandes estruturas de dados, principalmente na produção de jogos.

Na próxima parte da série iremos abordar o parão Facade (ou “Façade”, como pronunciado em alguns lugares).

Um abraço a todos e até a próxima.