Flyweight Pattern
Category
Structural Design Pattern
Overview
The Flyweight Pattern is a structural design pattern that focuses on reducing memory consumption and improving performance by sharing as much data as possible among similar objects. Instead of creating multiple objects with the same intrinsic properties, the Flyweight Pattern allows those properties to be shared, while extrinsic properties unique to each object are stored externally.
This pattern is particularly useful when:
A large number of objects share common data.
Object creation and memory usage need optimization.
The intrinsic state of an object is immutable and can be shared.
Key Characteristics
Intrinsic and Extrinsic State:
Intrinsic State: Shared among objects and remains immutable.
Extrinsic State: Varies between objects and is passed externally.
Memory Optimization:
Reduces the number of objects in memory by sharing instances of objects with similar intrinsic state.
Object Factory:
A central factory ensures the reuse of existing flyweights or creates new ones if needed.
Immutability:
Flyweight objects typically have immutable intrinsic state, making them thread-safe.
Scalability:
Effectively handles large datasets where many objects have similar data.
UML Diagram
The UML diagram below illustrates the Flyweight Pattern. A Flyweight interface defines common operations, and a FlyweightFactory manages shared instances.
Implementation Walkthrough
Participants
Flyweight:
Defines the interface for objects that can share intrinsic state.
Concrete Flyweight:
Implements the Flyweight interface and contains intrinsic state.
Flyweight Factory:
Manages the creation and sharing of flyweight objects.
Client:
Uses flyweights and manages extrinsic state.
Example: Reusing Tree Objects in a Forest
Flyweight Interface
/**
* @brief Flyweight interface for tree types.
*/
public interface TreeType {
void draw(int x, int y);
}
Concrete Flyweight
/**
* @brief Concrete implementation of the TreeType flyweight.
*
* Stores intrinsic state shared by all trees of this type.
*/
public class ConcreteTreeType implements TreeType {
private final String name;
private final String color;
private final String texture;
public ConcreteTreeType(String name, String color, String texture) {
this.name = name;
this.color = color;
this.texture = texture;
}
@Override
public void draw(int x, int y) {
System.out.println("Drawing tree [" + name + ", " + color + ", " + texture + "] at (" + x + ", " + y + ")");
}
}
Flyweight Factory
/**
* @brief Factory for managing and reusing TreeType instances.
*/
public class TreeFactory {
private static final Map<String, TreeType> treeTypes = new HashMap<>();
public static TreeType getTreeType(String name, String color, String texture) {
String key = name + "_" + color + "_" + texture;
return treeTypes.computeIfAbsent(key, k -> new ConcreteTreeType(name, color, texture));
}
}
Tree Class (Client-Side Object)
/**
* @brief Client-side object that combines intrinsic and extrinsic state.
*/
public class Tree {
private final int x;
private final int y;
private final 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);
}
}
Client Code: Forest
/**
* @brief Manages a collection of trees and utilizes the Flyweight pattern.
*/
public class Forest {
private final List<Tree> trees = new ArrayList<>();
public void plantTree(int x, int y, String name, String color, String texture) {
TreeType type = TreeFactory.getTreeType(name, color, texture);
trees.add(new Tree(x, y, type));
}
public void draw() {
for (Tree tree : trees) {
tree.draw();
}
}
}
Client Code Example
public class FlyweightPatternDemo {
public static void main(String[] args) {
Forest forest = new Forest();
// Plant trees with shared TreeType
forest.plantTree(1, 1, "Oak", "Green", "Rough");
forest.plantTree(2, 3, "Pine", "Dark Green", "Smooth");
forest.plantTree(4, 5, "Oak", "Green", "Rough"); // Reuses the "Oak" type
// Draw the forest
forest.draw();
}
}
Output
Drawing tree [Oak, Green, Rough] at (1, 1)
Drawing tree [Pine, Dark Green, Smooth] at (2, 3)
Drawing tree [Oak, Green, Rough] at (4, 5)
Applications
When to Use the Flyweight Pattern
Memory Optimization:
When an application needs to manage a large number of similar objects.
Shared Data:
When objects share significant portions of data.
Immutable State:
When the shared state is constant and can be reused safely.
Common Use Cases
Game Development:
Representing large numbers of entities (e.g., trees, characters) with shared attributes.
Text Editors:
Managing character formatting where glyph shapes are shared.
Data Caching:
Reusing objects in data-intensive applications.
Advantages and Disadvantages
Advantages
Memory Efficiency:
Reduces memory consumption by sharing objects with the same intrinsic state.
Improved Performance:
Reduces the cost of creating and managing large numbers of similar objects.
Scalability:
Enables the handling of large datasets with minimal resource overhead.
Disadvantages
Increased Complexity:
Requires additional code to manage the flyweight objects and their states.
Limited Flexibility:
Intrinsic state must be immutable, which can limit object behavior.
Coupling:
Introduces dependency on a centralized factory.
Key Takeaways
The Flyweight Pattern is a powerful solution for managing memory and optimizing performance when dealing with a large number of objects that share common data. While it introduces complexity through its use of intrinsic and extrinsic states, it is invaluable in resource-constrained or performance-critical applications.