C# -  Padrões Estruturais Gof - Flyweight


 Neste artigo vou apresentar o padrão estrutural Gof Flyweight.

O padrão Flyweight é usado para criar muitos objetos pequenos relacionados sem invocar muito trabalho de sobrecarga ao fazer isso, melhorando assim o desempenho e a capacidade de manutenção.

Este padrão permite que os programas suportem grandes quantidades de objetos, mantendo um baixo o consumo de memória. Ele consegue isso compartilhando partes do estado do objeto entre vários objetos, ele economiza RAM armazenando em cache os mesmos dados usados por objetos diferentes.

Este padrão é usado quando há a necessidade de criar um grande número de objetos parecidos, e , como criar um grande número de objetos consome uma muita memória, o padrão Flyweight fornece uma solução para minimizar o uso de recursos reduzindo a carga na memória e compartilhando objetos.

Objetos Flyweights

Os objetos compartilhados neste padrão são chamados Flyweights, e, a chave para criarmos os objetos compartilhados é a distinção entre o estado intrínseco e extrínseco de um objeto.

Assim cada objeto Flyweight possui duas partes :

  1. O estado intrínseco (interior) que é constante e é armazenado com o próprio objeto Flyweight
  2. O estado extrínseco (exterior) que não é constante e precisa ser calculado em tempo de execução e não é armazenado na memória

Com base nisso este padrão permite que muitas instâncias de um objeto compartilhem seu estado intrínseco e, assim, reduzem o custo associado à sua criação.

Exemplo de aplicação do padrão Flyweight

Vamos ilustrar a atuação do padrão com o seguinte exemplo:

Suponha que criamos e armazenamos um objeto Circulo no cache (que estamos representando na figura abaixo)



O objeto Circulo armazenado no cache não tem cor.

Agora suponha que temos que criar 50.000 objetos Circulo com a cor Amarela e 50.000 objetos Circulo na cor Vermelha
e 50.000 objetos Circulo com a cor Azul :



Observe que todas as formas são as mesmas -  temos somente objetos Circulo -  o que muda é apenas a cor.

Com certeza criar todos esses objetos vai consumir muita memória e isso é um problema que temos que resolver. Assim este cenário pode ser replicado quando você tem um grande numero de objetos que precisa criar.

O padrão Flyweight tenta melhorar o desempenho da seguinte forma:

- Ele cria o objeto círculo apenas uma vez e reutiliza esse objeto círculo várias vezes para criar um objeto com cor diferente;



Assim para obter os 50.000 objetos Circulo com a cor Amarela ao invés de criar novos objetos Circulo toda vez e preenche-los com a cor amarela podemos obter o objeto circulo do cache e preencher esse objeto com a cor amarela e podemos adotar o mesmo procedimento para criar os demais objetos com a cor diferente.



Desta forma podemos melhorar o desempenho da aplicação pois estamos reduzindo a criação dos objetos compartilhando
partes do estado do objeto.

Neste exemplo a Forma do objeto, círculo, é constante (não sofre alteração), assim ela refere-se ao estado intrínseco do objeto e portanto, ela é armazenada na memória, ou seja, no cache.

A Cor não é constante e refere-se ao estado extrínseco do objeto sendo calculada em tempo de execução e não é armazenada na memória.

Diagrama UML

O diagrama UML do padrão Flyweight segundo o Gof apresenta os seguintes participantes


1- Flyweight : Interface que define os membros dos objetos flyweight; Permite o compartilhamento, mas não o impõe. Os objetos concretos que implementam esta interface podem ser compartilhados ou não compartilhados;

2- ConcreteFlyweight : Implementa a interface Flyweight, adiciona armazenamento para o estado intrínseco.  Deve ser compartilhável e qualquer estado que vamos armazenar neste objeto deve estar em um estado intrínseco.

3- UnsharedConcreteFlyweight : Implementa a interface Flyweight e adiciona armazenamento para a instância particular e não é compartilhada

4-FlyweightFactory :  Cria e gerencia objetos flyweight.  Assegura que os objetos flyweight são compartilhados de forma correta. Quando um cliente solicita um flyweight, o objeto FlyweightFactory ativa uma instância existente ou cria uma, se não houver nenhuma.

O uso de UnSharedFlyweight nem sempre é necessário, depende dos requisitos.

Nota: Embora este padrão utilize uma fabrica para criar a correta implementação da interface Flyweight ele não deve ser confundido com o padrão Factory

Quando podemos usar o padrão Flyweight

Podemos usar este padrão :

- Quando muitos objetos semelhantes serão usados e o custo de armazenamento for alto

- Quando você puder compartilhar estado entre objetos;

- Quando alguns objetos compartilhados facilmente substituiriam muitos objetos não compartilhados

- Quando você quer economizar memória.

Vantagens do padrão Flyweight

Como vantagens deste padrão temos que :

- Reduz o uso de memória compartilhando objetos pesados.
- Favore o Cache de dados aprimorado para maior tempo de resposta.
- Aumenta o desempenho reduzindo o número de objetos pesados na memória

Desvantagem

Os objetos Flyweights pode introduzir custos de tempo de execução associados à transferência, localização e/ou computação do estado extrínseco, especialmente se ele foi anteriormente armazenado como estado intrínseco.

Além disso, a aplicação do padrão possui um escopo de aplicação reduzido e de acordo como Gof 5 condições devem ser consideradas para que o os benefícios do padrão sejam tangíveis.

Aplicação prática do padrão Flyweight

A seguir veremos um exemplo pratico de aplicação do padrão Flyweight.

Suponha que precisamos criar 50.000 objetos Circulos com diferentes cores em uma aplicação.  Assim teremos que criar :

10.000 objetos Círculos
- Amarelos
- Verdes
- Azuis
- Vermelhos
- Pretos

Cada objeto Circulo possui os seguintes atributos :

- Cor (não é constante)
- Coordenada X (fixo)
- Coordenada Y (fixo)
- Raio (fixo)

Com base nestas informações temos que :

- A Cor não é constante e representa o estado extrínseco do objeto flyweight e vai ser atribuída em tempo de execução e não será compartilhada;

- Forma, Coordenadas e o Raio são constantes e representam os estados intrínsecos e serão armazenados no cache;

Levando em conta este cenário e estas considerações vamos implementar o padrão Flyweight usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.

A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:


A interface IForma representa o Flyweight e possui o método Desenhar que define os objetos flyweight;

A classe concreta Circulo representa o ConcreteFlyweight e implementa a interface IForma e adiciona armazenamento para o estado intrínseco. (x, y, raio, forma) no cache;

A classe concreta FormaFactory representa a FlyweightFactory e cria e compartilha objetos flyweight;

A classe Program  representa o Client e usa a implementação do padrão com referencias a FormaFactory;

A seguir temos o código usado na implementação:

1- Interface IForma (Flyweight)

    public interface IForma
    {
        void Desenhar();
    }

A interface IForma que define  o contrato com a assinatura do método Desenhar que vai permitir criar os objetos flyweight  que no nosso exemplo serão apenas círculos.

2- Classe Circulo (ConcreteFlyweight)

    public class Circulo : IForma
    {
        //estado extrínseco
        public string Cor { get; set; }
        //estado intrínseco (cache)
        private int x = 10;
        private int y = 20;
        private int raio = 30;
        public void SetCor(string Cor)
        {
            this.Cor = Cor;
        }
        public void Desenhar()
        {
            Console.WriteLine($"Circulo: Desenhar() [Cor:{Cor} x:{x},y:{y}, raio:{raio}]");
        }
    }

A classe circulo implementa a interface e o método Desenhar  para criar um objeto Circulo e define um método SetCor para poder atribuir o valor a propriedade Cor que representa o estado extrínseco e não será compartilhado.  Esta classe também define os campos das coordenadas x, y e o raio que representam o estado intrínseco (ou seja são constantes) e serão armazenados no cache.

3- Classe FormaFactory (FlyweightFactory)

using System;
using System.Collections.Generic;

namespace Flyweight2
{
    public class FormaFactory
    {
        private static Dictionary<string, IForma> formas = new Dictionary<string, IForma>();

        // cria e gerencia objetos
        public static IForma GetForma(string chave)
        {
            IForma forma;
            if (formas.ContainsKey(chave))
            {
                return formas[chave];
            }
            else
            {
                if (chave == "circulo")
                {
                    forma = new Circulo();
                    formas.Add("circulo", forma);
                }
                else
                {
                    throw new Exception("Este tipo de objeto não pode ser criado");
                }
            }
            return forma;
        }
    }
}  

Usa um objeto Dictionary(estático) como uma coleção de objetos Forma para armazenar em cache os Círculos criados.

Define o método GetForma (estático) que vai receber a chave e vai verificar, com base na chave, se o objeto flyweight está no cache ou não. Se estiver lá, ele retornará o objeto flyweight existente.  E se não estiver lá, ele criará um novo objeto flyweight e adicionará esse objeto ao cache e retornará esse objeto flyweight.

4- Program

using System;

namespace Flyweight2
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("\n### Círculos Amarelos ");
            for (int i = 0; i < 3; i++)
            {
                var circulo = (Circulo)FormaFactory.GetForma("circulo");
                circulo.SetCor("Amarelo");
                circulo.Desenhar();
            }
            Console.WriteLine("\n### Círculos Verdes ");
            for (int i = 0; i < 3; i++)
            {
                var circulo = (Circulo)FormaFactory.GetForma("circulo");
                circulo.SetCor("Verde");
                circulo.Desenhar();
            }
            Console.WriteLine("\n### Círculos Azuis");
            for (int i = 0; i < 3; ++i)
            {
                var circulo = (Circulo)FormaFactory.GetForma("circulo");
                circulo.SetCor("Azul");
                circulo.Desenhar();
            }
            Console.WriteLine("\n### Círculos Vermelhos");
            for (int i = 0; i < 3; ++i)
            {
                var circulo = (Circulo)FormaFactory.GetForma("circulo");
                circulo.SetCor("Vermelho");
                circulo.Desenhar();
            }
            Console.WriteLine("\n### Círculos Pretos");
            for (int i = 0; i < 3; ++i)
            {
                var circulo = (Circulo)FormaFactory.GetForma("circulo");
                circulo.SetCor("Preto");
                circulo.Desenhar();
            }

            Console.ReadKey();
        }
    }
}

Neste código temos o seguinte :

- A classe Program representa o client e aqui vamos simular a criação de objetos usando um laço for para criar apenas 3 objetos de cada cor (apenas para vermos a implementação funcionando)

- Chama o método GetForma passando a chave que é aqui é a string circulo e realiza o Cast para o objeto Circulo

- Atribui a cor (estado extrinseco) desenha o Circulo usando o método Desenhar

- Aqui iremos verificar se o objeto ja foi criado e esta no cache se estiver retorna senao cria um novo objeto e adiciona ao cache

A execução do projeto irá apresentar o seguinte resultado:

Pegue o código do projeto aqui :   Flyweight1.zip

"(Disse Jesus) Eu sou a videira verdadeira, e meu Pai é o agricultor.
Todo ramo que, estando em mim, não der fruto, ele o corta; e todo o que dá fruto limpa, para que produza mais fruto ainda."

João 15:1,2

Referências:


José Carlos Macoratti