Flyweight Pattern
The Flyweight Pattern is a structural design pattern that focuses on minimizing memory usage by sharing as much as possible with similar objects. It's particularly useful when you need to create a large number of similar objects that would otherwise consume a significant amount of memory.
The pattern achieves efficiency by separating an object's intrinsic (shared) state from its extrinsic (unique) state, allowing the sharing of common parts among multiple objects.
Problem
Imagine you're developing a forest visualization for a game, where you need to render thousands of tree objects. Each tree requires memory for its model, textures, and position data. Creating individual objects for each tree would consume an enormous amount of memory, making the application slow and inefficient.
Components
- Flyweight: A shared object that can be used in multiple contexts simultaneously. It stores only the intrinsic (shared) state.
- Flyweight Factory: Creates and manages flyweight objects, ensuring that they are shared properly.
- Context: Contains the extrinsic (unique) state that makes each object instance different.
- Client: Maintains references to flyweights and computes or stores the extrinsic state.
Implementation Example
Let's implement a forest rendering system using the Flyweight pattern:
// Flyweight: TreeType class stores shared state
class TreeType {
constructor(name, color, texture) {
this.name = name;
this.color = color;
this.texture = texture;
}
render(canvas, x, y, age) {
// Render a tree of this type at the specified position
console.log(`Rendering ${this.name} tree at (${x},${y}) with age ${age}`);
// Draw using the shared properties (color, texture) and unique properties (position, age)
}
}
// Flyweight Factory: TreeFactory manages flyweight objects
class TreeFactory {
constructor() {
this.treeTypes = {};
}
getTreeType(name, color, texture) {
// Get or create a flyweight tree type
const key = `${name}-${color}-${texture}`;
if (!this.treeTypes[key]) {
this.treeTypes[key] = new TreeType(name, color, texture);
console.log(`Created new tree type: ${key}`);
}
return this.treeTypes[key];
}
getTreeTypesCount() {
return Object.keys(this.treeTypes).length;
}
}
// Context: Tree class contains extrinsic state
class Tree {
constructor(x, y, age, treeType) {
this.x = x;
this.y = y;
this.age = age;
this.treeType = treeType;
}
render(canvas) {
this.treeType.render(canvas, this.x, this.y, this.age);
}
}
// Client: Forest manages the trees
class Forest {
constructor() {
this.trees = [];
this.treeFactory = new TreeFactory();
}
plantTree(x, y, age, name, color, texture) {
const treeType = this.treeFactory.getTreeType(name, color, texture);
const tree = new Tree(x, y, age, treeType);
this.trees.push(tree);
return tree;
}
render(canvas) {
this.trees.forEach(tree => tree.render(canvas));
}
getStats() {
return {
totalTrees: this.trees.length,
uniqueTreeTypes: this.treeFactory.getTreeTypesCount(),
memorySaved: this.trees.length - this.treeFactory.getTreeTypesCount()
};
}
}
Interactive Demo: Forest Renderer
This demo shows how the Flyweight pattern can be used to efficiently render thousands of trees by sharing common tree properties.
When to Use
- When an application needs to create a large number of similar objects
- When memory usage is a concern due to the sheer quantity of objects
- When objects can have their state split into shared (intrinsic) and unique (extrinsic) parts
- When most of an object's state can be made extrinsic (context-specific)
Benefits
- Dramatically reduces memory usage when working with many similar objects
- Improves performance by reducing the amount of data kept in memory
- Centralizes state management for a group of objects
- Allows applications to support vast quantities of fine-grained objects
Real-World Uses
- Text editors and word processors for character formatting
- Game development for shared assets like textures and 3D models
- Graphics applications for managing sprite sheets
- UI components with shared styling
- Network connection pools in server applications