Flyweight Pattern
Flyweight Pattern: Sharing Objects Efficiently
Section titled “Flyweight Pattern: Sharing Objects Efficiently”Now let’s explore the Flyweight Pattern - a powerful structural design pattern that minimizes memory usage by sharing intrinsic state among multiple objects.
Why Flyweight Pattern?
Section titled “Why Flyweight Pattern?”Imagine you’re playing a video game with thousands of trees. Each tree has the same texture, color, and shape (intrinsic state), but different positions (extrinsic state). Instead of storing thousands of tree objects with duplicate data, you store one tree object and share it! The Flyweight Pattern works the same way - it shares common data (intrinsic state) and stores unique data (extrinsic state) separately.
The Flyweight Pattern uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic state (shared) from extrinsic state (unique).
What’s the Use of Flyweight Pattern?
Section titled “What’s the Use of Flyweight Pattern?”The Flyweight Pattern is useful when:
- You have many similar objects - That share common data
- Memory is a concern - Need to reduce memory usage
- Intrinsic state can be shared - Common data that doesn’t change
- Extrinsic state is unique - Unique data for each object
- You want to reduce object creation - Share objects instead of creating new ones
What Happens If We Don’t Use Flyweight Pattern?
Section titled “What Happens If We Don’t Use Flyweight Pattern?”Without the Flyweight Pattern, you might:
- High memory usage - Each object stores all data, even if shared
- Object explosion - Creating many similar objects wastes memory
- Poor performance - More objects means more memory allocation
- Wasted resources - Duplicate data stored multiple times
- Scalability issues - Can’t handle large numbers of objects
Simple Example: The Text Editor
Section titled “Simple Example: The Text Editor”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Flyweight Pattern works in practice - showing how flyweights are shared:
sequenceDiagram
participant Client
participant Factory as FlyweightFactory
participant Flyweight as CharacterFlyweight
participant Context as CharacterContext
Client->>Factory: get_flyweight('A')
activate Factory
Factory->>Factory: Check if 'A' exists
alt 'A' doesn't exist
Factory->>Flyweight: Create new Character('A')
activate Flyweight
Flyweight-->>Factory: Character('A')
deactivate Flyweight
Factory->>Factory: Store in cache
end
Factory-->>Client: Character('A')
deactivate Factory
Client->>Context: Create context(x=10, y=20)
activate Context
Context-->>Client: Context
deactivate Context
Client->>Flyweight: render(context)
activate Flyweight
Flyweight->>Flyweight: Use intrinsic: 'A'
Flyweight->>Flyweight: Use extrinsic: x, y from context
Flyweight-->>Client: Rendered 'A' at (10, 20)
deactivate Flyweight
Note over Client,Context: Same flyweight reused\nwith different contexts!
The Problem
Section titled “The Problem”You’re building a text editor. You need to render thousands of characters. Without Flyweight Pattern:
# ❌ Without Flyweight Pattern - High memory usage!
class Character: """Character with all data stored""" def __init__(self, char: str, font: str, size: int, x: int, y: int, color: str): self.char = char # Intrinsic (shared) self.font = font # Intrinsic (shared) self.size = size # Intrinsic (shared) self.x = x # Extrinsic (unique) self.y = y # Extrinsic (unique) self.color = color # Extrinsic (unique)
def render(self): print(f"Rendering '{self.char}' at ({self.x}, {self.y}) with {self.font}")
# Problem: Each character stores all data, even if shared!def render_text(text: str): characters = [] for i, char in enumerate(text): # Creating new object for each character - wastes memory! character = Character(char, "Arial", 12, i * 10, 0, "black") characters.append(character)
for char in characters: char.render()
# Problems:# - Each 'A' stores font, size separately (duplicate data!)# - 1000 'A' characters = 1000 objects with duplicate intrinsic data# - High memory usage# - Poor performance// ❌ Without Flyweight Pattern - High memory usage!
public class Character { // Character with all data stored private char character; // Intrinsic (shared) private String font; // Intrinsic (shared) private int size; // Intrinsic (shared) private int x; // Extrinsic (unique) private int y; // Extrinsic (unique) private String color; // Extrinsic (unique)
public Character(char character, String font, int size, int x, int y, String color) { this.character = character; this.font = font; this.size = size; this.x = x; this.y = y; this.color = color; }
public void render() { System.out.println("Rendering '" + character + "' at (" + x + ", " + y + ") with " + font); }}
// Problem: Each character stores all data, even if shared!public static void renderText(String text) { List<Character> characters = new ArrayList<>(); for (int i = 0; i < text.length(); i++) { // Creating new object for each character - wastes memory! Character character = new Character(text.charAt(i), "Arial", 12, i * 10, 0, "black"); characters.add(character); }
for (Character character : characters) { character.render(); }}
// Problems:// - Each 'A' stores font, size separately (duplicate data!)// - 1000 'A' characters = 1000 objects with duplicate intrinsic data// - High memory usage// - Poor performanceProblems:
- High memory usage - Each object stores all data, even shared data
- Object explosion - Creating many similar objects wastes memory
- Duplicate data - Same intrinsic data stored multiple times
- Poor scalability - Can’t handle large numbers of objects efficiently
The Solution: Flyweight Pattern
Section titled “The Solution: Flyweight Pattern”Class Structure
Section titled “Class Structure”classDiagram
class FlyweightFactory {
-flyweights: Map
+get_flyweight(key) Flyweight
}
class Flyweight {
<<interface>>
+operation(extrinsic) void
}
class ConcreteFlyweight {
-intrinsic_state: string
+operation(extrinsic) void
}
class UnsharedConcreteFlyweight {
-all_state: string
+operation(extrinsic) void
}
class Client {
+operation() void
}
Client --> FlyweightFactory : uses
FlyweightFactory --> Flyweight : creates/returns
Flyweight <|.. ConcreteFlyweight : implements
Flyweight <|.. UnsharedConcreteFlyweight : implements
Client --> Flyweight : uses with extrinsic state
note for ConcreteFlyweight "Stores intrinsic state (shared)"
note for Client "Provides extrinsic state (unique)"
from typing import Dict, Tuple
# Step 1: Define the Flyweight interfaceclass CharacterFlyweight: """Flyweight interface - stores intrinsic state"""
def render(self, x: int, y: int, color: str) -> None: """Render character using intrinsic state and extrinsic parameters""" raise NotImplementedError
# Step 2: Implement Concrete Flyweightclass Character(CharacterFlyweight): """Concrete Flyweight - stores intrinsic state (shared)"""
def __init__(self, char: str, font: str, size: int): # Intrinsic state - shared among many objects self.char = char self.font = font self.size = size
def render(self, x: int, y: int, color: str) -> None: """Render using intrinsic state + extrinsic parameters""" print(f"Rendering '{self.char}' at ({x}, {y}) with {self.font} size {self.size} in {color}")
# Step 3: Create Flyweight Factoryclass CharacterFactory: """Flyweight Factory - creates and manages flyweights"""
def __init__(self): # Cache of flyweights - shared objects self._flyweights: Dict[Tuple[str, str, int], Character] = {}
def get_character(self, char: str, font: str = "Arial", size: int = 12) -> Character: """Get or create flyweight character""" key = (char, font, size)
# Return existing flyweight if it exists if key not in self._flyweights: print(f"Creating new flyweight for '{char}' with {font} size {size}") self._flyweights[key] = Character(char, font, size) else: print(f"Reusing existing flyweight for '{char}' with {font} size {size}")
return self._flyweights[key]
# Step 4: Client code - uses flyweights with extrinsic stateclass TextEditor: """Client - uses flyweights with extrinsic state"""
def __init__(self): self.factory = CharacterFactory() self.characters = [] # Store (flyweight, extrinsic_state) pairs
def add_character(self, char: str, x: int, y: int, color: str, font: str = "Arial", size: int = 12): """Add character - reuses flyweight if possible""" # Get flyweight (shared intrinsic state) flyweight = self.factory.get_character(char, font, size)
# Store with extrinsic state (unique per character) self.characters.append({ 'flyweight': flyweight, 'x': x, 'y': y, 'color': color })
def render(self): """Render all characters""" print(f"\nRendering {len(self.characters)} characters:\n") for char_data in self.characters: # Use flyweight with extrinsic state char_data['flyweight'].render( char_data['x'], char_data['y'], char_data['color'] )
# Usage - Memory efficient!def main(): editor = TextEditor()
# Add many characters - flyweights are reused! text = "HELLO WORLD" for i, char in enumerate(text): editor.add_character(char, i * 10, 0, "black")
# Add same characters with different positions - reuses flyweights! for i, char in enumerate("HELLO"): editor.add_character(char, i * 10, 20, "blue")
editor.render()
print(f"\n✅ Flyweight Pattern: Created {len(editor.factory._flyweights)} flyweights") print(f" for {len(editor.characters)} characters (memory efficient!)")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define the Flyweight interfaceinterface CharacterFlyweight { // Flyweight interface - stores intrinsic state void render(int x, int y, String color);}
// Step 2: Implement Concrete Flyweightclass Character implements CharacterFlyweight { // Concrete Flyweight - stores intrinsic state (shared) private char character; // Intrinsic state - shared private String font; // Intrinsic state - shared private int size; // Intrinsic state - shared
public Character(char character, String font, int size) { this.character = character; this.font = font; this.size = size; }
@Override public void render(int x, int y, String color) { // Render using intrinsic state + extrinsic parameters System.out.println("Rendering '" + character + "' at (" + x + ", " + y + ") with " + font + " size " + size + " in " + color); }}
// Step 3: Create Flyweight Factoryclass CharacterFactory { // Flyweight Factory - creates and manages flyweights private Map<String, CharacterFlyweight> flyweights = new HashMap<>();
public CharacterFlyweight getCharacter(char character, String font, int size) { // Get or create flyweight character String key = character + "_" + font + "_" + size;
// Return existing flyweight if it exists if (!flyweights.containsKey(key)) { System.out.println("Creating new flyweight for '" + character + "' with " + font + " size " + size); flyweights.put(key, new Character(character, font, size)); } else { System.out.println("Reusing existing flyweight for '" + character + "' with " + font + " size " + size); }
return flyweights.get(key); }
public int getFlyweightCount() { return flyweights.size(); }}
// Step 4: Client code - uses flyweights with extrinsic stateclass TextEditor { // Client - uses flyweights with extrinsic state private CharacterFactory factory; private List<Map<String, Object>> characters;
public TextEditor() { this.factory = new CharacterFactory(); this.characters = new ArrayList<>(); }
public void addCharacter(char character, int x, int y, String color, String font, int size) { // Add character - reuses flyweight if possible // Get flyweight (shared intrinsic state) CharacterFlyweight flyweight = factory.getCharacter(character, font, size);
// Store with extrinsic state (unique per character) Map<String, Object> charData = new HashMap<>(); charData.put("flyweight", flyweight); charData.put("x", x); charData.put("y", y); charData.put("color", color); characters.add(charData); }
public void render() { // Render all characters System.out.println("\nRendering " + characters.size() + " characters:\n"); for (Map<String, Object> charData : characters) { // Use flyweight with extrinsic state CharacterFlyweight flyweight = (CharacterFlyweight) charData.get("flyweight"); int x = (Integer) charData.get("x"); int y = (Integer) charData.get("y"); String color = (String) charData.get("color"); flyweight.render(x, y, color); } }}
// Usage - Memory efficient!public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor();
// Add many characters - flyweights are reused! String text = "HELLO WORLD"; for (int i = 0; i < text.length(); i++) { editor.addCharacter(text.charAt(i), i * 10, 0, "black", "Arial", 12); }
// Add same characters with different positions - reuses flyweights! String text2 = "HELLO"; for (int i = 0; i < text2.length(); i++) { editor.addCharacter(text2.charAt(i), i * 10, 20, "blue", "Arial", 12); }
editor.render();
System.out.println("\n✅ Flyweight Pattern: Created " + editor.factory.getFlyweightCount() + " flyweights"); System.out.println(" for " + editor.characters.size() + " characters (memory efficient!)"); }}Real-World Software Example: Game Tree Rendering
Section titled “Real-World Software Example: Game Tree Rendering”Now let’s see a realistic software example - a game that needs to render thousands of trees efficiently.
The Problem
Section titled “The Problem”You’re building a game that needs to render thousands of trees. Each tree has the same texture and model (intrinsic), but different positions (extrinsic). Without Flyweight Pattern:
# ❌ Without Flyweight Pattern - High memory usage!
class Tree: """Tree with all data stored""" def __init__(self, x: int, y: int, texture: str, model: str, height: float): self.x = x # Extrinsic (unique) self.y = y # Extrinsic (unique) self.texture = texture # Intrinsic (shared - same for all trees!) self.model = model # Intrinsic (shared - same for all trees!) self.height = height # Intrinsic (shared - same for all trees!)
def render(self): print(f"Rendering tree at ({self.x}, {self.y}) with texture {self.texture}")
# Problem: Each tree stores texture and model separately!def render_forest(): trees = [] for i in range(1000): # Creating 1000 tree objects with duplicate texture/model data! tree = Tree(i * 10, i * 5, "oak_texture.png", "oak_model.obj", 5.0) trees.append(tree)
for tree in trees: tree.render()
# Problems:# - 1000 trees = 1000 objects storing same texture/model (waste!)# - High memory usage# - Poor performance// ❌ Without Flyweight Pattern - High memory usage!
public class Tree { // Tree with all data stored private int x; // Extrinsic (unique) private int y; // Extrinsic (unique) private String texture; // Intrinsic (shared - same for all trees!) private String model; // Intrinsic (shared - same for all trees!) private double height; // Intrinsic (shared - same for all trees!)
public Tree(int x, int y, String texture, String model, double height) { this.x = x; this.y = y; this.texture = texture; this.model = model; this.height = height; }
public void render() { System.out.println("Rendering tree at (" + x + ", " + y + ") with texture " + texture); }}
// Problem: Each tree stores texture and model separately!public static void renderForest() { List<Tree> trees = new ArrayList<>(); for (int i = 0; i < 1000; i++) { // Creating 1000 tree objects with duplicate texture/model data! Tree tree = new Tree(i * 10, i * 5, "oak_texture.png", "oak_model.obj", 5.0); trees.add(tree); }
for (Tree tree : trees) { tree.render(); }}
// Problems:// - 1000 trees = 1000 objects storing same texture/model (waste!)// - High memory usage// - Poor performanceProblems:
- High memory usage - Each tree stores texture and model separately
- Duplicate data - Same intrinsic data stored 1000 times
- Poor scalability - Can’t handle large numbers efficiently
The Solution: Flyweight Pattern
Section titled “The Solution: Flyweight Pattern”from typing import Dict, Tuple
# Step 1: Define Flyweightclass TreeType: """Flyweight - stores intrinsic state (shared)"""
def __init__(self, name: str, texture: str, model: str, height: float): # Intrinsic state - shared among many trees self.name = name self.texture = texture self.model = model self.height = height
def render(self, x: int, y: int): """Render tree using intrinsic state + extrinsic position""" print(f"Rendering {self.name} tree at ({x}, {y}) with texture {self.texture}")
# Step 2: Create Flyweight Factoryclass TreeTypeFactory: """Flyweight Factory - creates and manages tree types"""
def __init__(self): # Cache of tree types - shared objects self._tree_types: Dict[str, TreeType] = {}
def get_tree_type(self, name: str, texture: str, model: str, height: float) -> TreeType: """Get or create tree type flyweight""" key = f"{name}_{texture}_{model}_{height}"
if key not in self._tree_types: print(f"Creating new tree type: {name}") self._tree_types[key] = TreeType(name, texture, model, height) else: print(f"Reusing tree type: {name}")
return self._tree_types[key]
def get_type_count(self) -> int: """Get number of unique tree types""" return len(self._tree_types)
# Step 3: Client - uses flyweights with extrinsic stateclass Tree: """Tree - stores extrinsic state and reference to flyweight"""
def __init__(self, x: int, y: int, tree_type: TreeType): # Extrinsic state - unique per tree self.x = x self.y = y # Reference to flyweight - shared self.tree_type = tree_type
def render(self): """Render tree using flyweight""" self.tree_type.render(self.x, self.y)
class Forest: """Forest - manages many trees efficiently"""
def __init__(self): self.factory = TreeTypeFactory() self.trees = []
def plant_tree(self, x: int, y: int, name: str, texture: str, model: str, height: float): """Plant a tree - reuses tree type if possible""" # Get flyweight (shared intrinsic state) tree_type = self.factory.get_tree_type(name, texture, model, height)
# Create tree with extrinsic state tree = Tree(x, y, tree_type) self.trees.append(tree)
def render(self): """Render all trees""" print(f"\nRendering forest with {len(self.trees)} trees:\n") for tree in self.trees: tree.render()
# Usage - Memory efficient!def main(): forest = Forest()
# Plant 1000 oak trees - reuses same tree type! for i in range(1000): forest.plant_tree(i * 10, i * 5, "Oak", "oak_texture.png", "oak_model.obj", 5.0)
# Plant 500 pine trees - reuses same tree type! for i in range(500): forest.plant_tree(i * 8, i * 6, "Pine", "pine_texture.png", "pine_model.obj", 8.0)
forest.render()
print(f"\n✅ Flyweight Pattern: Created {forest.factory.get_type_count()} tree types") print(f" for {len(forest.trees)} trees (memory efficient!)") print(f" Memory saved: ~{(len(forest.trees) - forest.factory.get_type_count()) * 100}%")
if __name__ == "__main__": main()import java.util.*;
// Step 1: Define Flyweightclass TreeType { // Flyweight - stores intrinsic state (shared) private String name; // Intrinsic state - shared private String texture; // Intrinsic state - shared private String model; // Intrinsic state - shared private double height; // Intrinsic state - shared
public TreeType(String name, String texture, String model, double height) { this.name = name; this.texture = texture; this.model = model; this.height = height; }
public void render(int x, int y) { // Render tree using intrinsic state + extrinsic position System.out.println("Rendering " + name + " tree at (" + x + ", " + y + ") with texture " + texture); }}
// Step 2: Create Flyweight Factoryclass TreeTypeFactory { // Flyweight Factory - creates and manages tree types private Map<String, TreeType> treeTypes = new HashMap<>();
public TreeType getTreeType(String name, String texture, String model, double height) { // Get or create tree type flyweight String key = name + "_" + texture + "_" + model + "_" + height;
if (!treeTypes.containsKey(key)) { System.out.println("Creating new tree type: " + name); treeTypes.put(key, new TreeType(name, texture, model, height)); } else { System.out.println("Reusing tree type: " + name); }
return treeTypes.get(key); }
public int getTypeCount() { return treeTypes.size(); }}
// Step 3: Client - uses flyweights with extrinsic stateclass Tree { // Tree - stores extrinsic state and reference to flyweight private int x; // Extrinsic state - unique per tree private int y; // Extrinsic state - unique per tree private TreeType treeType; // Reference to flyweight - shared
public Tree(int x, int y, TreeType treeType) { this.x = x; this.y = y; this.treeType = treeType; }
public void render() { // Render tree using flyweight treeType.render(x, y); }}
class Forest { // Forest - manages many trees efficiently private TreeTypeFactory factory; private List<Tree> trees;
public Forest() { this.factory = new TreeTypeFactory(); this.trees = new ArrayList<>(); }
public void plantTree(int x, int y, String name, String texture, String model, double height) { // Plant a tree - reuses tree type if possible // Get flyweight (shared intrinsic state) TreeType treeType = factory.getTreeType(name, texture, model, height);
// Create tree with extrinsic state Tree tree = new Tree(x, y, treeType); trees.add(tree); }
public void render() { // Render all trees System.out.println("\nRendering forest with " + trees.size() + " trees:\n"); for (Tree tree : trees) { tree.render(); } }}
// Usage - Memory efficient!public class Main { public static void main(String[] args) { Forest forest = new Forest();
// Plant 1000 oak trees - reuses same tree type! for (int i = 0; i < 1000; i++) { forest.plantTree(i * 10, i * 5, "Oak", "oak_texture.png", "oak_model.obj", 5.0); }
// Plant 500 pine trees - reuses same tree type! for (int i = 0; i < 500; i++) { forest.plantTree(i * 8, i * 6, "Pine", "pine_texture.png", "pine_model.obj", 8.0); }
forest.render();
System.out.println("\n✅ Flyweight Pattern: Created " + forest.factory.getTypeCount() + " tree types"); System.out.println(" for " + forest.trees.size() + " trees (memory efficient!)"); System.out.println(" Memory saved: ~" + ((forest.trees.size() - forest.factory.getTypeCount()) * 100 / forest.trees.size()) + "%"); }}Flyweight Pattern Variants
Section titled “Flyweight Pattern Variants”There are different ways to implement the Flyweight Pattern:
1. Intrinsic/Extrinsic Separation (Standard)
Section titled “1. Intrinsic/Extrinsic Separation (Standard)”Intrinsic state in flyweight, extrinsic state passed as parameters:
# Standard Flyweight - intrinsic in object, extrinsic as parametersclass Flyweight: def __init__(self, intrinsic): self.intrinsic = intrinsic # Stored in object
def operation(self, extrinsic): # Extrinsic passed as parameter return f"{self.intrinsic} + {extrinsic}"// Standard Flyweight - intrinsic in object, extrinsic as parametersclass Flyweight { private String intrinsic; // Stored in object
public Flyweight(String intrinsic) { this.intrinsic = intrinsic; }
public String operation(String extrinsic) { // Extrinsic passed as parameter return intrinsic + " + " + extrinsic; }}Pros: Clear separation, efficient
Cons: Need to pass extrinsic state each time
2. Context Object
Section titled “2. Context Object”Extrinsic state stored in separate context object:
# Context-based Flyweight - extrinsic in context objectclass Context: def __init__(self, extrinsic): self.extrinsic = extrinsic
class Flyweight: def __init__(self, intrinsic): self.intrinsic = intrinsic
def operation(self, context: Context): return f"{self.intrinsic} + {context.extrinsic}"// Context-based Flyweight - extrinsic in context objectclass Context { private String extrinsic;
public Context(String extrinsic) { this.extrinsic = extrinsic; }
public String getExtrinsic() { return extrinsic; }}
class Flyweight { private String intrinsic;
public Flyweight(String intrinsic) { this.intrinsic = intrinsic; }
public String operation(Context context) { return intrinsic + " + " + context.getExtrinsic(); }}Pros: Better organization, can store multiple extrinsic values
Cons: More objects to manage
When to Use Flyweight Pattern?
Section titled “When to Use Flyweight Pattern?”Use Flyweight Pattern when:
✅ You have many similar objects - That share common data
✅ Memory is a concern - Need to reduce memory usage
✅ Intrinsic state can be shared - Common data that doesn’t change
✅ Extrinsic state is unique - Unique data for each object
✅ You want to reduce object creation - Share objects instead of creating new ones
When NOT to Use Flyweight Pattern?
Section titled “When NOT to Use Flyweight Pattern?”Don’t use Flyweight Pattern when:
❌ Few objects - If you have few objects, overhead isn’t worth it
❌ No shared state - If objects don’t share common data
❌ Extrinsic state is large - If extrinsic state is larger than intrinsic
❌ Over-engineering - Don’t add complexity for simple cases
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Not Separating Intrinsic and Extrinsic State
Section titled “Mistake 1: Not Separating Intrinsic and Extrinsic State”# ❌ Bad: Not separating intrinsic and extrinsic stateclass Flyweight: def __init__(self, intrinsic, extrinsic): # Bad: Storing both! self.intrinsic = intrinsic self.extrinsic = extrinsic # Bad: Should be passed as parameter
# ✅ Good: Separate intrinsic and extrinsicclass Flyweight: def __init__(self, intrinsic): self.intrinsic = intrinsic # Good: Only intrinsic stored
def operation(self, extrinsic): # Good: Extrinsic passed as parameter return f"{self.intrinsic} + {extrinsic}"// ❌ Bad: Not separating intrinsic and extrinsic stateclass Flyweight { private String intrinsic; private String extrinsic; // Bad: Storing both!
public Flyweight(String intrinsic, String extrinsic) { this.intrinsic = intrinsic; this.extrinsic = extrinsic; // Bad: Should be passed as parameter }}
// ✅ Good: Separate intrinsic and extrinsicclass Flyweight { private String intrinsic; // Good: Only intrinsic stored
public Flyweight(String intrinsic) { this.intrinsic = intrinsic; }
public String operation(String extrinsic) { // Good: Extrinsic passed as parameter return intrinsic + " + " + extrinsic; }}Mistake 2: Not Using Factory
Section titled “Mistake 2: Not Using Factory”# ❌ Bad: Creating flyweights directlyflyweight1 = Flyweight("A") # Bad: No sharing!flyweight2 = Flyweight("A") # Bad: Creates duplicate!
# ✅ Good: Using factoryfactory = FlyweightFactory()flyweight1 = factory.get_flyweight("A") # Good: Creates and cachesflyweight2 = factory.get_flyweight("A") # Good: Reuses cached// ❌ Bad: Creating flyweights directlyFlyweight flyweight1 = new Flyweight("A"); // Bad: No sharing!Flyweight flyweight2 = new Flyweight("A"); // Bad: Creates duplicate!
// ✅ Good: Using factoryFlyweightFactory factory = new FlyweightFactory();Flyweight flyweight1 = factory.getFlyweight("A"); // Good: Creates and cachesFlyweight flyweight2 = factory.getFlyweight("A"); // Good: Reuses cachedMistake 3: Storing Too Much in Intrinsic State
Section titled “Mistake 3: Storing Too Much in Intrinsic State”# ❌ Bad: Storing unique data in intrinsic stateclass Flyweight: def __init__(self, char, x, y): # Bad: x, y are unique! self.char = char self.x = x # Bad: Should be extrinsic! self.y = y # Bad: Should be extrinsic!
# ✅ Good: Only shared data in intrinsic stateclass Flyweight: def __init__(self, char): # Good: Only shared data self.char = char
def render(self, x, y): # Good: Unique data as parameters print(f"{self.char} at ({x}, {y})")// ❌ Bad: Storing unique data in intrinsic stateclass Flyweight { private char character; private int x; // Bad: Should be extrinsic! private int y; // Bad: Should be extrinsic!
public Flyweight(char character, int x, int y) { this.character = character; this.x = x; // Bad: Unique per object! this.y = y; // Bad: Unique per object! }}
// ✅ Good: Only shared data in intrinsic stateclass Flyweight { private char character; // Good: Only shared data
public Flyweight(char character) { this.character = character; }
public void render(int x, int y) { // Good: Unique data as parameters System.out.println(character + " at (" + x + ", " + y + ")"); }}Benefits of Flyweight Pattern
Section titled “Benefits of Flyweight Pattern”- Memory Efficiency - Shares intrinsic state, reduces memory usage
- Reduced Object Creation - Reuses flyweights instead of creating new objects
- Scalability - Can handle large numbers of objects efficiently
- Performance - Less memory allocation and garbage collection
- Separation of Concerns - Clear separation of intrinsic vs extrinsic state
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Flyweight Pattern?
Section titled “What is Flyweight Pattern?”Flyweight Pattern is a structural design pattern that uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic state (shared) from extrinsic state (unique).
Why Use It?
Section titled “Why Use It?”- ✅ Memory efficiency - Shares intrinsic state, reduces memory usage
- ✅ Reduced object creation - Reuses flyweights instead of creating new objects
- ✅ Scalability - Can handle large numbers of objects efficiently
- ✅ Performance - Less memory allocation and garbage collection
- ✅ Separation - Clear separation of intrinsic vs extrinsic state
How It Works?
Section titled “How It Works?”- Identify intrinsic state - Data that can be shared
- Identify extrinsic state - Data that is unique per object
- Create flyweight - Stores only intrinsic state
- Create factory - Manages flyweight creation and caching
- Client uses flyweight - Passes extrinsic state as parameters
Key Components
Section titled “Key Components”Client → FlyweightFactory → Flyweight (intrinsic) + Extrinsic State- Flyweight - Stores intrinsic state (shared)
- FlyweightFactory - Creates and manages flyweights
- Client - Provides extrinsic state (unique)
- Intrinsic State - Shared data (e.g., character, texture)
- Extrinsic State - Unique data (e.g., position, color)
Simple Example
Section titled “Simple Example”class Flyweight: def __init__(self, intrinsic): self.intrinsic = intrinsic # Shared
def operation(self, extrinsic): return f"{self.intrinsic} + {extrinsic}" # UniqueWhen to Use?
Section titled “When to Use?”✅ Many similar objects
✅ Memory is a concern
✅ Intrinsic state can be shared
✅ Extrinsic state is unique
✅ Want to reduce object creation
When NOT to Use?
Section titled “When NOT to Use?”❌ Few objects
❌ No shared state
❌ Extrinsic state is large
❌ Over-engineering
Key Takeaways
Section titled “Key Takeaways”- Flyweight Pattern = Shares intrinsic state, stores extrinsic separately
- Intrinsic = Shared data (stored in flyweight)
- Extrinsic = Unique data (passed as parameters)
- Benefit = Memory efficiency, scalability
- Use Case = Many similar objects (text editors, games, graphics)
Common Pattern Structure
Section titled “Common Pattern Structure”class Flyweight: def __init__(self, intrinsic): self.intrinsic = intrinsic
def operation(self, extrinsic): return f"{self.intrinsic} + {extrinsic}"
class Factory: def __init__(self): self.cache = {}
def get_flyweight(self, key): if key not in self.cache: self.cache[key] = Flyweight(key) return self.cache[key]Remember
Section titled “Remember”- Flyweight Pattern shares intrinsic state
- It separates intrinsic from extrinsic state
- Use a factory to manage flyweights
- It’s about memory efficiency, not just sharing!
- Only store truly shared data in intrinsic state!
Interview Focus: Flyweight Pattern
Section titled “Interview Focus: Flyweight Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Flyweight Pattern is a structural design pattern that uses sharing to support large numbers of fine-grained objects efficiently. It separates intrinsic state (shared data) from extrinsic state (unique data), storing only intrinsic state in the flyweight object.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of intrinsic vs extrinsic state
- Indicates you can explain concepts clearly
2. When to Use Flyweight Pattern
Section titled “2. When to Use Flyweight Pattern”Must mention:
- ✅ Many similar objects - That share common data
- ✅ Memory is a concern - Need to reduce memory usage
- ✅ Intrinsic state can be shared - Common data that doesn’t change
- ✅ Extrinsic state is unique - Unique data for each object
Example scenario to give:
“I’d use Flyweight Pattern when building a text editor that needs to render thousands of characters. Each character ‘A’ has the same shape and font (intrinsic), but different positions (extrinsic). Instead of creating 1000 ‘A’ objects, I create one ‘A’ flyweight and reuse it with different positions. This saves significant memory.”
3. Intrinsic vs Extrinsic State
Section titled “3. Intrinsic vs Extrinsic State”Must discuss:
- Intrinsic State: Shared data stored in flyweight (e.g., character, texture, model)
- Extrinsic State: Unique data passed as parameters (e.g., position, color)
- Key difference: Intrinsic is shared, extrinsic is unique
Example to give:
“In a game with trees, intrinsic state includes texture and model (same for all oak trees), while extrinsic state includes position (unique per tree). The flyweight stores texture and model, and position is passed when rendering.”
4. Benefits and Trade-offs
Section titled “4. Benefits and Trade-offs”Benefits to mention:
- Memory efficiency - Shares intrinsic state, reduces memory usage
- Reduced object creation - Reuses flyweights instead of creating new objects
- Scalability - Can handle large numbers of objects efficiently
- Performance - Less memory allocation and garbage collection
Trade-offs to acknowledge:
- Complexity - Adds abstraction layer
- Factory overhead - Need to manage flyweight cache
- Not suitable for few objects - Overhead not worth it for small numbers
5. Common Interview Questions
Section titled “5. Common Interview Questions”Q: “What’s the difference between Flyweight Pattern and Object Pool Pattern?”
A:
“Flyweight Pattern shares intrinsic state among many objects to reduce memory usage. Object Pool Pattern reuses entire objects to avoid expensive creation. Flyweight is about sharing data, Object Pool is about reusing objects. Flyweight separates intrinsic/extrinsic state, Object Pool manages a pool of ready-to-use objects.”
Q: “How do you determine what should be intrinsic vs extrinsic state?”
A:
“Intrinsic state is data that is the same across many objects and doesn’t change. Extrinsic state is data that is unique per object or changes frequently. If data is shared and immutable, it’s intrinsic. If data is unique or mutable, it’s extrinsic. For example, character shape is intrinsic (same for all ‘A’s), position is extrinsic (unique per ‘A’).”
Q: “How does Flyweight Pattern relate to memory management?”
A:
“Flyweight Pattern reduces memory usage by sharing common data. Instead of storing duplicate data in each object, it stores shared data once and reuses it. This reduces memory footprint, especially when you have many similar objects. It also reduces garbage collection pressure by creating fewer objects.”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Flyweight Pattern clearly in one sentence
- Explain intrinsic vs extrinsic state (with examples)
- Describe when to use it (with examples showing memory savings)
- Implement Flyweight Pattern from scratch
- Compare with other patterns (Object Pool, Singleton)
- List benefits and trade-offs
- Identify common mistakes (not separating state, no factory)
- Give 2-3 real-world examples
- Discuss memory management implications
- Explain when NOT to use it
Remember: Flyweight Pattern is about sharing intrinsic state efficiently - reducing memory usage by sharing common data among many objects! 🪶