Patrones de Diseño (XII): Patrones Estructurales - Flyweight

En esta nueva entrega vamos a hablar del patrón Flyweight, que trata de aumentar el rendimiento de aplicaciones en las que se usan multitud de objetos similares.

El fundamento de este patrón es que los objetos que van a repetirse contengan características comunes. El objetivo es eliminar la redundancia exisente mediante la compartición de las características comunes.

Pasemos a ver la plantilla de este patrón para entender mejor su objetivo:

Flyweight

Nombre: Flyweight

Problema y Contexto:

Nuestro sistema tiene un tipo de componente que se repite numerosas veces, y las instancias tienen una serie de características en común. Queremos optimizar el tamaño en memoria que ocupa para sacar el máximo partido al sistema y no desaprovechar los recursos con datos redundantes.

Se aplica cuando:

  • Se necesita eliminar o reducir la redundancia cuando se tiene gran cantidad de objetos que comparten bastante información.
  • Nuestro soporte tiene memoria limitada y esta necesita ser aprovechada óptimamente.
  • La identidad propia de los objetos es irrelevante.

Solución y Estructura:

Para solucionar este escenario, debemos de abstraer las características del elemento que se replica en 2 grupo: las intrínsecas y las extrínsecas. Las primeras hacen referencia a los estados comunes que tiene el objeto o grupo de objetos a replicar, mientras que las segundas aluden a las características propias de la instancia.

De esta manera, nuestor objetivo será estudiar las características comunes de los objetos y crear una clase extrínseca. Luego a la hora de realizar las diferentes instancias, cojeremos la parte común (intrínseca) y la complementaremos con los datos específicos de la instancia.

La estructura es la siguiente:

https://static.dzone.com/dz1/dz-files/flyweight_pattern.png

Donde:

Client: Trabaja con una referencia a un Flyweight. Establece los estados extrínsecos de los objtos.

FlyweightFactory: Crea y maneja objetos flyweight asegurándose que se compartan adecuadamente. Cuando el cliente solicita un flyweight, FlyweightFactory proporciona una instancia existente y si no existe la crea.

Flyweight: Interfaz que contiene métodos para el establecimiento, acceso y modificación de las propiedades extrínsecas. Deberá ser implementada por las instancias del Flyweight.

ConcreteFlyweight: Implementa la interfaz Flyweight y añade el almacenamiento de las características intrínsecas (si las hay) y deben poder ser compartidos.

UnsharedConcreteFlyweight: Todos los Flyweight no tienen por qué ser compartidos, la implementación de la interfaz habilita la compratición pero no la fuerza. Es común que los objetos UnsharedConcreteFlyweight tengan hijos ConcreteFlyweight en algún punto de la jerarquía.


Consecuencias

  • POSITIVAS:
    • Simplifica el uso de sistemas complejos con tareas redundantes.
    • Oculta al cliente la complejidad real del sistema.
    • Reduce el acoplamiento entre el subsistema y los clientes.
  • NEGATIVAS:
    • Aumenta la complejidad de los objetos.
    • Aumenta el número de clases del sistema.

Patrones Relacionados: Abstract Factory, Composite, State y Strategy.

Ejemplo:

Vamos a aplicar lo visto en un ejemplo. Este patrón es muy utilizado en videojuegos, ya que nos permite que elementos con muchas características en común, como pueden ser los árboles del escenario, puedan ser reproducidos sin despilfarrar la memoria que gastarían haciendo una instancia por cada uno.

Para desarrollar el escenario anterior vamos a definir en primer lugar la interfaz Flyweight. Tendremos determinados tipos de árboles que se dibujarán centenas de veces a lo largo de nuestros escenarios, por lo que nos interesa la característica extrínseca tipo.


public interface IArbolLigero
{
public String getTipo();
public void dibujar( long x, long y, long z );
}


Ahora crearemos el ConcreteFlyweight, que en este caso representará al arbol concreto (definiendo las características intrínsecas del mismo):


public class Arbol implements IArbolLigero
{
private String tipo;

public Arbol( String tipo )
{
this.tipo = tipo;
}

@Override
public String getTipo()
{
return this.tipo;
}

@Override
public void dibujar( long x, long y, long z )
{
System.out.println("Árbol [" + this.getTipo() + "] dibujado en las coordenadas ("+x+", "+y+", "+z+")" );
}
}


El patrón Flyweight combina un Singleton con una Factoría para lograr proporcionar un punto de acceso único a la clase desde el cual se puedan reutilizar los elementos no redundantes. Por tanto, nuestra FlyweightFactory quedará definida como:


public class FabricaDeArboles
{
private Map arboles;

public FabricaDeArboles()
{
this.arboles = new HashMap();
}

public IArbolLigero getArbol( String tipo )
{
// Si no tenemos ningún árbol del tipo solicitado, lo creamos
if(!arboles.containsKey(tipo))
{
arboles.put(tipo, new Arbol(tipo));
}

// Devolvemos el árbol del tipo correspondiente
return arboles.get(tipo);
}
}


Una vez definida la fábrica de árboles ya tenemos todos los ingredientes. Definiremos ahora nuestro cliente como si fuera el motor de un videojuego que tiene que llenar un paisaje de pinos, abetos y sauces:

public static void main(String args)
{
// Obtenemos el número de árboles a dibujar por parámetro
int num_arboles = Integer.PatronesInt(args[0]);

// Definimos los tipos de árbol
String[] tipos = {"pino","abeto","sauce"};

// Creamos la fábrica de Árboles
FabricaDeArboles f = new FabricaDeArboles();

// Crearemos tantos árboles como se indiquen por parámetro
// El tipo de árbol será seleccionado aleatoriamente
Random r = new Random(tipos.length),
p = new Random();

for(int i=0;i<num_arboles;i++)
{
f.getArbol(tipos[r.nextInt()])
.dibujar(
p.nextLong(),
p.nextLong(),
p.nextLong()
);
}
}

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP