Flyweight Design Pattern in Java
Introduction
In large-scale software systems, creating a huge number of objects can consume significant memory, especially if many objects share common intrinsic data. The Flyweight Pattern addresses this issue by sharing objects that are similar to minimize memory usage while maintaining unique external states.
Think of it like a forest of trees in a game: instead of creating a separate object for each tree, trees of the same type share common characteristics (like texture and color) while only storing unique positions.
Definition
The Flyweight Pattern is a structural design pattern that reduces memory usage by sharing as much data as possible with similar objects.
Key Idea: Separate intrinsic state (shared) from extrinsic state (unique per object) and reuse objects whenever possible.
- Intrinsic State: Shared, stored inside the flyweight object.
- Extrinsic State: Unique to each object, passed from the client.
Intrinsic vs. Extrinsic State
1. Intrinsic State
- Definition:
The part of the object’s state that is invariant (does not change) and can be shared across multiple objects. - Stored Inside Flyweight Object
Since intrinsic data is common, it is kept inside the flyweight object itself. - Example in Real Life:
- In a text editor, the font style or character shape (like "Arial", "Times New Roman") is intrinsic. The same "A" shape can be reused everywhere.
- In a game with millions of trees, the tree type (e.g., "Oak", "Pine") is intrinsic because many trees share the same type.
2. Extrinsic State
- Definition:
The part of the object’s state that is variant (changes per object) and cannot be shared. - Passed from the Client
Since it depends on the context, extrinsic data is not stored inside the flyweight object. Instead, it is provided externally whenever needed. - Example in Real Life:
- In the text editor, the position of a character (row, column) is extrinsic because each "A" may appear at a different location.
- In the game example, the tree’s coordinates (x, y, z position) are extrinsic because every tree stands in a different place.
Why Separate These States?
The benefit of separating intrinsic and extrinsic state is memory optimization.
- If both intrinsic and extrinsic state were stored in each object, you would end up creating thousands of heavy objects.
- By storing only intrinsic (shared) data in a flyweight object and passing extrinsic (unique) data when needed, you can drastically cut down on memory usage.
Example in Java: Characters in a Document
// Flyweight
interface Character {
void display(int row, int column); // extrinsic state (position)
}
// Concrete Flyweight
class ConcreteCharacter implements Character {
private final char symbol; // intrinsic state
public ConcreteCharacter(char symbol) {
this.symbol = symbol;
}
@Override
public void display(int row, int column) {
System.out.println("Character: " + symbol +
" | Position: (" + row + ", " + column + ")");
}
}
// Flyweight Factory
class CharacterFactory {
private final java.util.Map pool = new java.util.HashMap<>();
public Character getCharacter(char symbol) {
pool.putIfAbsent(symbol, new ConcreteCharacter(symbol));
return pool.get(symbol);
}
}
// Client
public class FlyweightDemo {
public static void main(String[] args) {
CharacterFactory factory = new CharacterFactory();
Character a1 = factory.getCharacter('A'); // reused
Character a2 = factory.getCharacter('A'); // same object reused
Character b = factory.getCharacter('B');
a1.display(0, 1); // extrinsic state
a2.display(0, 2); // extrinsic state
b.display(0, 3); // extrinsic state
System.out.println("a1 and a2 are same object? " + (a1 == a2));
}
}
Output
Character: A | Position: (0, 1)
Character: A | Position: (0, 2)
Character: B | Position: (0, 3)
a1 and a2 are same object? true
Explanation of States in Example
- Intrinsic State (shared):
The actual character symbol 'A' or 'B'. This is stored inside the ConcreteCharacter flyweight object. - Extrinsic State (unique):
The position (row, column) of each character. This is passed by the client at runtime when calling display().
Real-World Applications
- Text Editors: Characters stored as flyweights; positions are extrinsic.
- Game Development: Trees, cars, bullets – type/model is intrinsic, location is extrinsic.
- GUI Systems: Icons/images are intrinsic, positions and states are extrinsic.
- Document Rendering (PDF/Word): Same font glyphs reused at different positions.
When to Use the Flyweight Pattern
- When many objects are needed that share common data.
- When memory usage is critical.
- When objects can be decomposed into intrinsic and extrinsic states.
- In gaming, graphics, or document editing applications where similar objects repeat frequently.
Real-World Example
- Text Editor: Each character on the screen shares the same font, size, and style (intrinsic state), but their position in the document is unique (extrinsic state).
- Chess Game: All white pawns share the same representation (intrinsic state) while their board positions differ (extrinsic state).
Java Implementation
Let’s implement a Flyweight Pattern example for drawing colored circles.
Step 1: Define the Flyweight Interface
// Flyweight interface
interface Shape {
void draw(int x, int y); // Extrinsic state passed here
}
Step 2: Create Concrete Flyweight
// Concrete Flyweight
class Circle implements Shape {
private String color; // Intrinsic state
public Circle(String color) {
this.color = color;
System.out.println("Creating a circle of color: " + color);
}
@Override
public void draw(int x, int y) {
System.out.println("Drawing " + color + " circle at (" + x + ", " + y + ")");
}
}
Step 3: Implement Flyweight Factory
import java.util.HashMap;
import java.util.Map;
// Flyweight Factory
class ShapeFactory {
private static final Map circleMap = new HashMap<>();
public static Shape getCircle(String color) {
Circle circle = (Circle) circleMap.get(color);
if (circle == null) {
circle = new Circle(color); // Create only if not exists
circleMap.put(color, circle);
}
return circle;
}
}
Step 4: Client Usage
public class Main {
public static void main(String[] args) {
Shape redCircle1 = ShapeFactory.getCircle("Red");
redCircle1.draw(10, 20);
Shape redCircle2 = ShapeFactory.getCircle("Red");
redCircle2.draw(30, 40);
Shape blueCircle = ShapeFactory.getCircle("Blue");
blueCircle.draw(50, 60);
}
}
Output:
Creating a circle of color: Red
Drawing Red circle at (10, 20)
Drawing Red circle at (30, 40)
Creating a circle of color: Blue
Drawing Blue circle at (50, 60)
Observation: The red circle object is reused, avoiding additional memory allocation.
Advantages of the Flyweight Pattern
- Memory Optimization: Reduces memory usage by reusing objects.
- High Performance: Less memory allocation leads to faster object creation.
- Scalable Systems: Suitable for systems with a huge number of similar objects.
- Separates Intrinsic and Extrinsic States: Makes object design cleaner and more maintainable.
Disadvantages
- Complex Implementation: Requires careful separation of intrinsic and extrinsic states.
- Clients Must Manage Extrinsic State: Mismanagement can lead to errors.
- Overhead of Object Management: Maintaining flyweight pools may increase code complexity.
Summary
The Flyweight Pattern is a powerful memory optimization technique for applications that require a large number of similar objects. By sharing intrinsic state and externalizing unique state, it provides scalability, efficiency, and flexibility.
Next, we will explore the Proxy Pattern, which controls access to objects by providing substitutes or placeholders, enabling lazy loading, access control, and security.