The Flyweight Pattern is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
The Problem: RAM Exhaustion
Imagine you are building a Forest Rendering Engine for a game on your website. You want to display 1,000,000 trees on the screen.
Each Tree object has:
- Intrinsic State: Data that is constant and shared (e.g., the tree's texture, 3D model, and color). This takes up a lot of memory.
- Extrinsic State: Data that varies (e.g., the specific X and Y coordinates for each tree).
If you create 1,000,000 separate objects, each containing the heavy texture data, your application will crash due to an Out of Memory error.
The Solution
We strip the heavy "Intrinsic" data out of the objects and put it into a single Flyweight object. The 1,000,000 tree instances will then simply hold a reference to that one shared Flyweight object and store only their unique coordinates.
Step-by-Step Java Implementation
1. The Flyweight (Intrinsic State)
This class contains the shared data (Tree Type, Color, Texture).
// TreeType.java
public class TreeType {
private String name;
private String color;
private String otherTreeData; // Imagine this is a heavy 3D model
public TreeType(String name, String color, String otherTreeData) {
this.name = name;
this.color = color;
this.otherTreeData = otherTreeData;
}
public void draw(int x, int y) {
System.out.println("Drawing " + name + " (" + color + ") at " + x + ", " + y);
}
}
2. The Flyweight Factory
This manages the Flyweight objects and ensures they are reused.
// TreeFactory.java
import java.util.HashMap;
import java.util.Map;
public class TreeFactory {
private static Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String otherData) {
String key = name + "_" + color;
if (!treeTypes.containsKey(key)) {
treeTypes.put(key, new TreeType(name, color, otherData));
System.out.println("Creating NEW tree type: " + name);
}
return treeTypes.get(key);
}
}
3. The Context (Extrinsic State)
The unique object that refers to the shared Flyweight.
// Tree.java
public class Tree {
private int x;
private int y;
private TreeType type; // Reference to Flyweight
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void draw() {
type.draw(x, y);
}
}
Full Code for Testing
// Save as FlyweightTest.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
// Flyweight: Shared state
class TreeType {
private String name;
private String color;
public TreeType(String name, String color) {
this.name = name;
this.color = color;
}
public void draw(int x, int y) {
System.out.println("Tree [" + name + " - " + color + "] at (" + x + "," + y + ")");
}
}
// Factory: Manages Flyweights
class TreeFactory {
private static Map<String, TreeType> types = new HashMap<>();
public static TreeType getTreeType(String name, String color) {
String key = name + color;
if (types.get(key) == null) {
types.put(key, new TreeType(name, color));
}
return types.get(key);
}
}
// Context: Unique state
class Tree {
private int x, y;
private TreeType type;
public Tree(int x, int y, TreeType type) {
this.x = x;
this.y = y;
this.type = type;
}
public void draw() { type.draw(x, y); }
}
public class FlyweightTest {
public static void main(String[] args) {
System.out.println("--- Forest Renderer (Flyweight) ---\n");
List<Tree> forest = new ArrayList<>();
// We want 5 trees, but only 2 types (Oak and Pine)
TreeType oak = TreeFactory.getTreeType("Oak", "Green");
TreeType pine = TreeFactory.getTreeType("Pine", "Dark Green");
forest.add(new Tree(10, 20, oak));
forest.add(new Tree(15, 25, oak));
forest.add(new Tree(50, 50, pine));
forest.add(new Tree(60, 10, pine));
forest.add(new Tree(12, 80, oak));
for (Tree tree : forest) {
tree.draw();
}
System.out.println("\n--- Memory Optimization Summary ---");
System.out.println("Total Trees created: " + forest.size());
System.out.println("Total TreeType (Flyweight) objects in memory: 2");
}
}
Why use Flyweight?
- Memory Efficiency: It is the primary way to handle millions of small objects without crashing the JVM.
- Centralized State: Changes to the Flyweight (e.g., changing the "Oak" texture) will instantly reflect across all 1,000,000 instances.
Real-World Example
- Java String Pool: When you create two identical strings using
String s = "hello", Java often uses the Flyweight pattern to point both variables to the same memory location to save space. - Text Editors: A character object for 'A' is a flyweight; it is shared by every 'A' on the page, with unique extrinsic state like (X, Y) positions.