El patrón Flyweight es uno de los patrones de diseño estructurales propuestos por la "Gang of Four" (GoF). Su objetivo principal es minimizar el uso de memoria al compartir la mayor cantidad posible de datos entre objetos similares. Este patrón es especialmente útil cuando se necesitan crear una gran cantidad de objetos similares, lo que puede consumir una cantidad considerable de memoria.
En este artículo, exploraremos cómo implementar el patrón Flyweight en TypeScript. Veremos qué es el patrón Flyweight, cuándo es adecuado usarlo y proporcionaremos un ejemplo detallado de implementación en TypeScript.
El patrón Flyweight se basa en la idea de compartir datos comunes entre múltiples objetos para reducir el uso de memoria. En lugar de crear un nuevo objeto para cada instancia, compartimos partes del objeto que son inmutables y comunes a múltiples instancias. Estas partes compartidas se llaman "Flyweights".
1. Flyweight: Define la interfaz que los flyweights concretos deben implementar.
2. ConcreteFlyweight: Implementa la interfaz de Flyweight y almacena los datos compartidos.
3. UnsharedConcreteFlyweight: Representa los objetos que no se comparten. Estos objetos pueden contener referencias a flyweights compartidos.
4. FlyweightFactory: Administra y crea flyweights. Asegura que los flyweights se compartan correctamente.
5. Client: Utiliza la fábrica para obtener instancias de flyweights y las utiliza.
El patrón Flyweight es útil en las siguientes situaciones:
A continuación, se presenta un ejemplo de cómo implementar el patrón Flyweight en TypeScript. En este ejemplo, crearemos un sistema para gestionar figuras geométricas (círculos) con diferentes colores.
Primero, definimos la interfaz Flyweight que las clases concretas implementarán.
interface Shape {
draw(x: number, y: number): void;
}
Implementamos la clase Circle
, que será el flyweight concreto. Esta clase almacenará los datos compartidos (el color).
class Circle implements Shape {
private color: string;
constructor(color: string) {
this.color = color;
}
draw(x: number, y: number): void {
console.log(`Drawing a ${this.color} circle at (${x}, ${y})`);
}
}
La fábrica ShapeFactory
será responsable de crear y gestionar los flyweights. Asegurará que los círculos con el mismo color se compartan.
class ShapeFactory {
private static circles: { [key: string]: Circle } = {};
public static getCircle(color: string): Circle {
if (!this.circles[color]) {
this.circles[color] = new Circle(color);
console.log(`Creating a ${color} circle`);
}
return this.circles[color];
}
}
Finalmente, utilizamos la fábrica para obtener los flyweights y dibujarlos.
function main() {
const colors = ["Red", "Green", "Blue", "Yellow", "Black"];
for (let i = 0; i < 20; i++) {
const circle = ShapeFactory.getCircle(colors[Math.floor(Math.random() * colors.length)]);
circle.draw(Math.floor(Math.random() * 100), Math.floor(Math.random() * 100));
}
}
main();
1. Interfaz Shape: Define el método draw
que todos los flyweights deben implementar.
2. Clase Circle: Implementa la interfaz Shape
y almacena el color como un dato compartido.
3. Clase ShapeFactory: Gestiona la creación y el almacenamiento de los flyweights. Si un círculo con un color específico ya existe, se reutiliza.
4. Función main: Genera círculos aleatorios con colores aleatorios y los dibuja en posiciones aleatorias.
Al ejecutar el código anterior, deberíamos ver mensajes que indican cuándo se crean nuevos círculos y cuándo se reutilizan. Por ejemplo:
Creating a Red circle
Drawing a Red circle at (47, 58)
Creating a Green circle
Drawing a Green circle at (23, 75)
Drawing a Red circle at (93, 44)
...
En este ejemplo, podemos ver que se crean nuevos círculos solo cuando es necesario, y se reutilizan cuando ya existen, ahorrando memoria.
El patrón Flyweight es una técnica poderosa para optimizar el uso de memoria en aplicaciones que manejan una gran cantidad de objetos similares. En este artículo, hemos visto cómo implementar el patrón Flyweight en TypeScript mediante un ejemplo práctico de figuras geométricas. Este patrón es particularmente útil en aplicaciones donde la eficiencia y el rendimiento son cruciales. Implementarlo adecuadamente puede resultar en una reducción significativa del uso de memoria y una mejora en el rendimiento general de la aplicación.
Jorge García
Fullstack developer