Flyweight
Propósito
Utilice la compartición para dar soporte a un gran número de objetos finos de forma eficiente.
Explicación
Un ejemplo real
La tienda del alquimista tiene estanterías llenas de pociones mágicas. Muchas de las pociones son las mismas, por lo
que no es necesario crear un nuevo objeto para cada una de ellas. En su lugar, una instancia de objeto puede
representar
múltiples elementos de la estantería para que la huella de memoria siga siendo pequeña.
En pocas palabras
Se utiliza para minimizar el uso de memoria o los gastos computacionales compartiendo todo lo posible con objetos
similares.
Wikipedia dice
En programación informática, flyweight es un patrón de diseño de software. Un flyweight es un objeto que minimiza el
uso de memoria compartiendo tantos datos como sea posible con otros objetos similares; es una forma de utilizar
objetos
en grandes cantidades cuando una simple representación repetida utilizaría una cantidad inaceptable de memoria.
Ejemplo programático
Traduciendo nuestro ejemplo de la tienda de alquimia de arriba. En primer lugar, tenemos diferentes tipos de
pociones Potion
: HealingPotion
, HolyWaterPotion
e InvisibilityPotion
:
public interface Potion {
void drink();
}
@Slf4j
public class HealingPotion implements Potion {
@Override
public void drink() {
LOGGER.info("You feel healed. (Potion={})", System.identityHashCode(this));
}
}
@Slf4j
public class HolyWaterPotion implements Potion {
@Override
public void drink() {
LOGGER.info("You feel blessed. (Potion={})", System.identityHashCode(this));
}
}
@Slf4j
public class InvisibilityPotion implements Potion {
@Override
public void drink() {
LOGGER.info("You become invisible. (Potion={})", System.identityHashCode(this));
}
}
Luego la clase Flyweight PotionFactory
, que es la fábrica para crear pociones.
public class PotionFactory {
private final Map<PotionType, Potion> potions;
public PotionFactory() {
potions = new EnumMap<>(PotionType.class);
}
Potion createPotion(PotionType type) {
var potion = potions.get(type);
if (potion == null) {
switch (type) {
case HEALING -> {
potion = new HealingPotion();
potions.put(type, potion);
}
case HOLY_WATER -> {
potion = new HolyWaterPotion();
potions.put(type, potion);
}
case INVISIBILITY -> {
potion = new InvisibilityPotion();
potions.put(type, potion);
}
default -> {
}
}
}
return potion;
}
}
AlchemistShop
contiene dos estantes de pociones mágicas. Las pociones se crean utilizando la antes
mencionada PotionFactory
.
@Slf4j
public class AlchemistShop {
private final List<Potion> topShelf;
private final List<Potion> bottomShelf;
public AlchemistShop() {
var factory = new PotionFactory();
topShelf = List.of(
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.STRENGTH),
factory.createPotion(PotionType.HEALING),
factory.createPotion(PotionType.INVISIBILITY),
factory.createPotion(PotionType.STRENGTH),
factory.createPotion(PotionType.HEALING),
factory.createPotion(PotionType.HEALING)
);
bottomShelf = List.of(
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.POISON),
factory.createPotion(PotionType.HOLY_WATER),
factory.createPotion(PotionType.HOLY_WATER)
);
}
public final List<Potion> getTopShelf() {
return List.copyOf(this.topShelf);
}
public final List<Potion> getBottomShelf() {
return List.copyOf(this.bottomShelf);
}
public void drinkPotions() {
LOGGER.info("Drinking top shelf potions\n");
topShelf.forEach(Potion::drink);
LOGGER.info("Drinking bottom shelf potions\n");
bottomShelf.forEach(Potion::drink);
}
}
En nuestro escenario, un valiente visitante entra en la tienda del alquimista y se bebe todas las pociones.
// create the alchemist shop with the potions
var alchemistShop = new AlchemistShop();
// a brave visitor enters the alchemist shop and drinks all the potions
alchemistShop.drinkPotions();
Salida del programa:
Drinking top shelf potions
You become invisible. (Potion=1509514333)
You become invisible. (Potion=1509514333)
You feel strong. (Potion=739498517)
You feel healed. (Potion=125130493)
You become invisible. (Potion=1509514333)
You feel strong. (Potion=739498517)
You feel healed. (Potion=125130493)
You feel healed. (Potion=125130493)
Drinking bottom shelf potions
Urgh! This is poisonous. (Potion=166239592)
Urgh! This is poisonous. (Potion=166239592)
Urgh! This is poisonous. (Potion=166239592)
You feel blessed. (Potion=991505714)
You feel blessed. (Potion=991505714)
Diagrama de clases
Aplicabilidad
La eficacia del patrón Flyweight depende en gran medida de cómo y dónde se utilice. Aplique el patrón
Flyweight cuando se cumplan todas las condiciones siguientes:
- Una aplicación utiliza un gran número de objetos.
- Los costes de almacenamiento son altos debido a la gran cantidad de objetos.
- La mayor parte del estado de los objetos puede hacerse extrínseco.
- Muchos grupos de objetos pueden ser reemplazados por relativamente pocos objetos compartidos una vez que el estado extrínseco
extrínseco. - La aplicación no depende de la identidad de los objetos. Dado que los objetos flyweight pueden ser compartidos, las pruebas de identidad
devolverán verdadero para objetos conceptualmente distintos.
Usos conocidos
- java.lang.Integer#valueOf(int) y
de forma similar para Byte, Carácter y otros tipos envueltos (Wrappers).