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:
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 performance// ❌ Without Flyweight Pattern - High memory usage!
class Character { /** Character with all data stored */ private char: string; // Intrinsic (shared) private font: string; // Intrinsic (shared) private size: number; // Intrinsic (shared) private x: number; // Extrinsic (unique) private y: number; // Extrinsic (unique) private color: string; // Extrinsic (unique)
constructor(char: string, font: string, size: number, x: number, y: number, color: string) { this.char = char; this.font = font; this.size = size; this.x = x; this.y = y; this.color = color; }
render(): void { console.log(`Rendering '${this.char}' at (${this.x}, ${this.y}) with ${this.font}`); }}
// Problem: Each character stores all data, even if shared!function renderText(text: string): void { const characters: Character[] = []; for (let i = 0; i < text.length; i++) { // Creating new object for each character - wastes memory! const character = new Character(text[i], "Arial", 12, i * 10, 0, "black"); characters.push(character); }
for (const char of 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!
#include <iostream>#include <string>#include <vector>
class Character {private: char character; // Intrinsic (shared) std::string font; // Intrinsic (shared) int size; // Intrinsic (shared) int x; // Extrinsic (unique) int y; // Extrinsic (unique) std::string color; // Extrinsic (unique)
public: Character(char character, const std::string& font, int size, int x, int y, const std::string& color) : character(character), font(font), size(size), x(x), y(y), color(color) {}
void render() { std::cout << "Rendering '" << character << "' at (" << x << ", " << y << ") with " << font << std::endl; }};
// Problem: Each character stores all data, even if shared!void renderText(const std::string& text) { std::vector<Character> characters; for (size_t i = 0; i < text.length(); i++) { // Creating new object for each character - wastes memory! Character character(text[i], "Arial", 12, i * 10, 0, "black"); characters.push_back(character); }
for (auto& 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 performance// ❌ Without Flyweight Pattern - High memory usage!
using System;using System.Collections.Generic;
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() { Console.WriteLine($"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 List<Character>(); for (int i = 0; i < text.Length; i++) { // Creating new object for each character - wastes memory! Character character = new Character(text[i], "Arial", 12, i * 10, 0, "black"); characters.Add(character); }
foreach (var character in 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 performance// ❌ Without Flyweight Pattern - High memory usage!
package main
import "fmt"
type BadCharacter struct { character rune // Intrinsic (shared) — but stored per object font string // Intrinsic (shared) — but stored per object size int // Intrinsic (shared) — but stored per object x, y int // Extrinsic (unique) color string // Extrinsic (unique)}
func (c *BadCharacter) Render() { fmt.Printf("Rendering '%c' at (%d, %d) with %s\n", c.character, c.x, c.y, c.font)}
// Problem: Each character stores all data, even if shared!func renderBadText(text string) { var characters []*BadCharacter for i, ch := range text { characters = append(characters, &BadCharacter{character: ch, font: "Arial", size: 12, x: i * 10, y: 0, color: "black"}) } for _, c := range characters { c.Render() }}
// Problems:// - Each 'A' stores font, size separately (duplicate data!)// - 1000 'A' characters = 1000 objects with duplicate intrinsic data// - High memory usage// The Problemstruct Tree { x: i32, y: i32, name: String, color: String, texture: Vec<u8>,}fn plant_forest() -> Vec<Tree> { (0..1000) .map(|i| Tree { x: i, y: i, name: "Oak".into(), color: "Green".into(), texture: vec![0; 1024], }) .collect()}Problems:
- 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”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!)"); }}// Step 1: Define the Flyweight interfaceinterface CharacterFlyweight { /** Flyweight interface - stores intrinsic state */ render(x: number, y: number, color: string): void;}
// Step 2: Implement Concrete Flyweightclass Character implements CharacterFlyweight { /** Concrete Flyweight - stores intrinsic state (shared) */ private character: string; // Intrinsic state - shared private font: string; // Intrinsic state - shared private size: number; // Intrinsic state - shared
constructor(character: string, font: string, size: number) { this.character = character; this.font = font; this.size = size; }
render(x: number, y: number, color: string): void { // Render using intrinsic state + extrinsic parameters console.log(`Rendering '${this.character}' at (${x}, ${y}) with ${this.font} size ${this.size} in ${color}`); }}
// Step 3: Create Flyweight Factoryclass CharacterFactory { /** Flyweight Factory - creates and manages flyweights */ private flyweights: Map<string, CharacterFlyweight> = new Map();
getCharacter(character: string, font: string, size: number): CharacterFlyweight { // Get or create flyweight character const key = `${character}_${font}_${size}`;
// Return existing flyweight if it exists if (!this.flyweights.has(key)) { console.log(`Creating new flyweight for '${character}' with ${font} size ${size}`); this.flyweights.set(key, new Character(character, font, size)); } else { console.log(`Reusing existing flyweight for '${character}' with ${font} size ${size}`); }
return this.flyweights.get(key)!; }
getFlyweightCount(): number { return this.flyweights.size; }}
// Step 4: Client code - uses flyweights with extrinsic stateclass TextEditor { /** Client - uses flyweights with extrinsic state */ public factory: CharacterFactory; public characters: Array<{flyweight: CharacterFlyweight, x: number, y: number, color: string}>;
constructor() { this.factory = new CharacterFactory(); this.characters = []; }
addCharacter(character: string, x: number, y: number, color: string, font: string = "Arial", size: number = 12): void { // Add character - reuses flyweight if possible // Get flyweight (shared intrinsic state) const flyweight = this.factory.getCharacter(character, font, size);
// Store with extrinsic state (unique per character) this.characters.push({ flyweight, x, y, color }); }
render(): void { // Render all characters console.log(`\nRendering ${this.characters.length} characters:\n`); for (const charData of this.characters) { // Use flyweight with extrinsic state charData.flyweight.render(charData.x, charData.y, charData.color); } }}
// Usage - Memory efficient!function main(): void { const editor = new TextEditor();
// Add many characters - flyweights are reused! const text = "HELLO WORLD"; for (let i = 0; i < text.length; i++) { editor.addCharacter(text[i], i * 10, 0, "black"); }
// Add same characters with different positions - reuses flyweights! for (let i = 0; i < "HELLO".length; i++) { editor.addCharacter("HELLO"[i], i * 10, 20, "blue"); }
editor.render();
console.log(`\n✅ Flyweight Pattern: Created ${editor.factory.getFlyweightCount()} flyweights`); console.log(` for ${editor.characters.length} characters (memory efficient!)`);}
main();#include <iostream>#include <string>#include <unordered_map>#include <vector>#include <memory>
// Step 1: Define the Flyweight interfaceclass CharacterFlyweight {public: virtual ~CharacterFlyweight() = default; // Flyweight interface - stores intrinsic state virtual void render(int x, int y, const std::string& color) const = 0;};
// Step 2: Implement Concrete Flyweightclass Character : public CharacterFlyweight {private: char character; // Intrinsic state - shared std::string font; // Intrinsic state - shared int size; // Intrinsic state - shared
public: Character(char character, const std::string& font, int size) : character(character), font(font), size(size) {}
void render(int x, int y, const std::string& color) const override { // Render using intrinsic state + extrinsic parameters std::cout << "Rendering '" << character << "' at (" << x << ", " << y << ") with " << font << " size " << size << " in " << color << std::endl; }};
// Step 3: Create Flyweight Factoryclass CharacterFactory {private: std::unordered_map<std::string, std::shared_ptr<CharacterFlyweight>> flyweights;
public: std::shared_ptr<CharacterFlyweight> getCharacter(char character, const std::string& font, int size) { // Get or create flyweight character std::string key = std::string(1, character) + "_" + font + "_" + std::to_string(size);
// Return existing flyweight if it exists if (flyweights.find(key) == flyweights.end()) { std::cout << "Creating new flyweight for '" << character << "' with " << font << " size " << size << std::endl; flyweights[key] = std::make_shared<Character>(character, font, size); } else { std::cout << "Reusing existing flyweight for '" << character << "' with " << font << " size " << size << std::endl; }
return flyweights[key]; }
int getFlyweightCount() const { return flyweights.size(); }};
// Step 4: Client code - uses flyweights with extrinsic stateclass TextEditor {public: CharacterFactory factory; struct CharData { std::shared_ptr<CharacterFlyweight> flyweight; int x, y; std::string color; }; std::vector<CharData> characters;
void addCharacter(char character, int x, int y, const std::string& color, const std::string& font = "Arial", int size = 12) { // Add character - reuses flyweight if possible // Get flyweight (shared intrinsic state) auto flyweight = factory.getCharacter(character, font, size);
// Store with extrinsic state (unique per character) characters.push_back({flyweight, x, y, color}); }
void render() { // Render all characters std::cout << "\nRendering " << characters.size() << " characters:\n" << std::endl; for (const auto& charData : characters) { // Use flyweight with extrinsic state charData.flyweight->render(charData.x, charData.y, charData.color); } }};
// Usage - Memory efficient!int main() { TextEditor editor;
// Add many characters - flyweights are reused! std::string text = "HELLO WORLD"; for (size_t i = 0; i < text.length(); i++) { editor.addCharacter(text[i], i * 10, 0, "black"); }
// Add same characters with different positions - reuses flyweights! std::string text2 = "HELLO"; for (size_t i = 0; i < text2.length(); i++) { editor.addCharacter(text2[i], i * 10, 20, "blue"); }
editor.render();
std::cout << "\n✅ Flyweight Pattern: Created " << editor.factory.getFlyweightCount() << " flyweights" << std::endl; std::cout << " for " << editor.characters.size() << " characters (memory efficient!)" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define the Flyweight interfacepublic interface ICharacterFlyweight{ /** Flyweight interface - stores intrinsic state */ void Render(int x, int y, string color);}
// Step 2: Implement Concrete Flyweightpublic class Character : ICharacterFlyweight{ /** 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; }
public void Render(int x, int y, string color) { // Render using intrinsic state + extrinsic parameters Console.WriteLine($"Rendering '{character}' at ({x}, {y}) with {font} size {size} in {color}"); }}
// Step 3: Create Flyweight Factorypublic class CharacterFactory{ /** Flyweight Factory - creates and manages flyweights */ private Dictionary<string, ICharacterFlyweight> flyweights = new Dictionary<string, ICharacterFlyweight>();
public ICharacterFlyweight 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)) { Console.WriteLine($"Creating new flyweight for '{character}' with {font} size {size}"); flyweights[key] = new Character(character, font, size); } else { Console.WriteLine($"Reusing existing flyweight for '{character}' with {font} size {size}"); }
return flyweights[key]; }
public int GetFlyweightCount() { return flyweights.Count; }}
// Step 4: Client code - uses flyweights with extrinsic statepublic class TextEditor{ /** Client - uses flyweights with extrinsic state */ public CharacterFactory factory; public List<(ICharacterFlyweight flyweight, int x, int y, string color)> characters;
public TextEditor() { this.factory = new CharacterFactory(); this.characters = new List<(ICharacterFlyweight, int, int, string)>(); }
public void AddCharacter(char character, int x, int y, string color, string font = "Arial", int size = 12) { // Add character - reuses flyweight if possible // Get flyweight (shared intrinsic state) ICharacterFlyweight flyweight = factory.GetCharacter(character, font, size);
// Store with extrinsic state (unique per character) characters.Add((flyweight, x, y, color)); }
public void Render() { // Render all characters Console.WriteLine($"\nRendering {characters.Count} characters:\n"); foreach (var charData in characters) { // Use flyweight with extrinsic state charData.flyweight.Render(charData.x, charData.y, charData.color); } }}
// Usage - Memory efficient!class Program{ static void Main() { 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[i], i * 10, 0, "black"); }
// Add same characters with different positions - reuses flyweights! string text2 = "HELLO"; for (int i = 0; i < text2.Length; i++) { editor.AddCharacter(text2[i], i * 10, 20, "blue"); }
editor.Render();
Console.WriteLine($"\n✅ Flyweight Pattern: Created {editor.factory.GetFlyweightCount()} flyweights"); Console.WriteLine($" for {editor.characters.Count} characters (memory efficient!)"); }}package main
import "fmt"
// Step 1: Flyweight - stores intrinsic state (shared)type CharacterFlyweight struct { character rune // Intrinsic state - shared font string // Intrinsic state - shared size int // Intrinsic state - shared}
func (c *CharacterFlyweight) Render(x, y int, color string) { fmt.Printf("Rendering '%c' at (%d, %d) with %s size %d in %s\n", c.character, x, y, c.font, c.size, color)}
// Step 2: Flyweight Factorytype CharacterFactory struct { flyweights map[string]*CharacterFlyweight}
func NewCharacterFactory() *CharacterFactory { return &CharacterFactory{flyweights: make(map[string]*CharacterFlyweight)}}
func (f *CharacterFactory) GetCharacter(ch rune, font string, size int) *CharacterFlyweight { key := fmt.Sprintf("%c_%s_%d", ch, font, size) if _, ok := f.flyweights[key]; !ok { fmt.Printf("Creating new flyweight for '%c' with %s size %d\n", ch, font, size) f.flyweights[key] = &CharacterFlyweight{character: ch, font: font, size: size} } else { fmt.Printf("Reusing flyweight for '%c'\n", ch) } return f.flyweights[key]}
func (f *CharacterFactory) Count() int { return len(f.flyweights) }
// Step 3: Client context - stores extrinsic state + reference to flyweighttype CharContext struct { flyweight *CharacterFlyweight x, y int color string}
// Step 4: Text Editortype TextEditor struct { factory *CharacterFactory characters []CharContext}
func NewTextEditor() *TextEditor { return &TextEditor{factory: NewCharacterFactory()}}
func (e *TextEditor) AddCharacter(ch rune, x, y int, color, font string, size int) { fw := e.factory.GetCharacter(ch, font, size) e.characters = append(e.characters, CharContext{flyweight: fw, x: x, y: y, color: color})}
func (e *TextEditor) Render() { fmt.Printf("\nRendering %d characters:\n\n", len(e.characters)) for _, ctx := range e.characters { ctx.flyweight.Render(ctx.x, ctx.y, ctx.color) }}
func main() { editor := NewTextEditor() text := "HELLO WORLD" for i, ch := range text { editor.AddCharacter(ch, i*10, 0, "black", "Arial", 12) } text2 := "HELLO" for i, ch := range text2 { editor.AddCharacter(ch, i*10, 20, "blue", "Arial", 12) } editor.Render() fmt.Printf("\n✅ Flyweight Pattern: Created %d flyweights\n", editor.factory.Count()) fmt.Printf(" for %d characters (memory efficient!)\n", len(editor.characters))}// Class Structureuse std::rc::Rc;struct TreeType { name: String, color: String,}struct Tree { x: i32, y: i32, tree_type: Rc<TreeType>,}fn main() { let oak = Rc::new(TreeType { name: "Oak".into(), color: "Green".into(), }); let tree = Tree { x: 10, y: 20, tree_type: Rc::clone(&oak), }; println!("{}", tree.tree_type.name);}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 performance// ❌ Without Flyweight Pattern - High memory usage!
class Tree { /** Tree with all data stored */ private x: number; // Extrinsic (unique) private y: number; // Extrinsic (unique) private texture: string; // Intrinsic (shared - same for all trees!) private model: string; // Intrinsic (shared - same for all trees!) private height: number; // Intrinsic (shared - same for all trees!)
constructor(x: number, y: number, texture: string, model: string, height: number) { this.x = x; this.y = y; this.texture = texture; this.model = model; this.height = height; }
render(): void { console.log(`Rendering tree at (${this.x}, ${this.y}) with texture ${this.texture}`); }}
// Problem: Each tree stores texture and model separately!function renderForest(): void { const trees: Tree[] = []; for (let i = 0; i < 1000; i++) { // Creating 1000 tree objects with duplicate texture/model data! const tree = new Tree(i * 10, i * 5, "oak_texture.png", "oak_model.obj", 5.0); trees.push(tree); }
for (const tree of trees) { tree.render(); }}
// Problems:// - 1000 trees = 1000 objects storing same texture/model (waste!)// - High memory usage// - Poor performance// ❌ Without Flyweight Pattern - High memory usage!
#include <iostream>#include <string>#include <vector>
class Tree {private: int x; // Extrinsic (unique) int y; // Extrinsic (unique) std::string texture; // Intrinsic (shared - same for all trees!) std::string model; // Intrinsic (shared - same for all trees!) double height; // Intrinsic (shared - same for all trees!)
public: Tree(int x, int y, const std::string& texture, const std::string& model, double height) : x(x), y(y), texture(texture), model(model), height(height) {}
void render() { std::cout << "Rendering tree at (" << x << ", " << y << ") with texture " << texture << std::endl; }};
// Problem: Each tree stores texture and model separately!void renderForest() { std::vector<Tree> trees; for (int i = 0; i < 1000; i++) { // Creating 1000 tree objects with duplicate texture/model data! Tree tree(i * 10, i * 5, "oak_texture.png", "oak_model.obj", 5.0); trees.push_back(tree); }
for (auto& tree : trees) { tree.render(); }}
// Problems:// - 1000 trees = 1000 objects storing same texture/model (waste!)// - High memory usage// - Poor performance// ❌ Without Flyweight Pattern - High memory usage!
using System;using System.Collections.Generic;
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() { Console.WriteLine($"Rendering tree at ({x}, {y}) with texture {texture}"); }}
// Problem: Each tree stores texture and model separately!public static void RenderForest(){ List<Tree> trees = new List<Tree>(); 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); }
foreach (var 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!
package main
import "fmt"
type BadTree struct { x, y int // Extrinsic (unique) texture string // Intrinsic (shared) — but stored per object model string // Intrinsic (shared) — but stored per object height float64 // Intrinsic (shared) — but stored per object}
func (t *BadTree) Render() { fmt.Printf("Rendering tree at (%d, %d) with texture %s\n", t.x, t.y, t.texture)}
func renderBadForest() { var trees []*BadTree for i := 0; i < 1000; i++ { // Creating 1000 tree objects with duplicate texture/model data! trees = append(trees, &BadTree{x: i * 10, y: i * 5, texture: "oak_texture.png", model: "oak_model.obj", height: 5.0}) } for _, t := range trees { t.Render() }}
// Problems:// - 1000 trees = 1000 objects storing same texture/model (waste!)// - High memory usage// The Problemstruct Tree { x: i32, y: i32, name: String, color: String, texture: Vec<u8>,}fn plant_forest() -> Vec<Tree> { (0..1000) .map(|i| Tree { x: i, y: i, name: "Oak".into(), color: "Green".into(), texture: vec![0; 1024], }) .collect()}Problems:
- 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()) + "%"); }}// Step 1: Define Flyweightclass TreeType { /** Flyweight - stores intrinsic state (shared) */ private name: string; private texture: string; private model: string; private height: number;
constructor(name: string, texture: string, model: string, height: number) { // Intrinsic state - shared among many trees this.name = name; this.texture = texture; this.model = model; this.height = height; }
render(x: number, y: number): void { // Render using intrinsic state + extrinsic parameters console.log(`Rendering ${this.name} tree at (${x}, ${y}) with texture ${this.texture}, model ${this.model}, height ${this.height}m`); }}
// Step 2: Create Flyweight Factoryclass TreeTypeFactory { /** Flyweight Factory - creates and manages tree types */ private treeTypes: Map<string, TreeType> = new Map();
getTreeType(name: string, texture: string, model: string, height: number): TreeType { // Get or create tree type flyweight const key = `${name}_${texture}_${model}_${height}`;
// Return existing tree type if it exists if (!this.treeTypes.has(key)) { console.log(`Creating new tree type: ${name}`); this.treeTypes.set(key, new TreeType(name, texture, model, height)); } else { console.log(`Reusing tree type: ${name}`); }
return this.treeTypes.get(key)!; }
getTypeCount(): number { return this.treeTypes.size; }}
// Step 3: Client - uses flyweights with extrinsic stateclass Tree { /** Tree - stores extrinsic state and reference to flyweight */ private x: number; // Extrinsic state - unique per tree private y: number; // Extrinsic state - unique per tree private treeType: TreeType; // Reference to flyweight - shared
constructor(x: number, y: number, treeType: TreeType) { this.x = x; this.y = y; this.treeType = treeType; }
render(): void { // Render tree using flyweight this.treeType.render(this.x, this.y); }}
class Forest { /** Forest - manages many trees efficiently */ public factory: TreeTypeFactory; public trees: Tree[];
constructor() { this.factory = new TreeTypeFactory(); this.trees = []; }
plantTree(x: number, y: number, name: string, texture: string, model: string, height: number): void { // Plant a tree - reuses tree type if possible // Get flyweight (shared intrinsic state) const treeType = this.factory.getTreeType(name, texture, model, height);
// Create tree with extrinsic state const tree = new Tree(x, y, treeType); this.trees.push(tree); }
render(): void { // Render all trees console.log(`\nRendering forest with ${this.trees.length} trees:\n`); for (const tree of this.trees) { tree.render(); } }}
// Usage - Memory efficient!function main(): void { const forest = new Forest();
// Plant 1000 oak trees - reuses same tree type! for (let 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 (let i = 0; i < 500; i++) { forest.plantTree(i * 8, i * 6, "Pine", "pine_texture.png", "pine_model.obj", 8.0); }
forest.render();
console.log(`\n✅ Flyweight Pattern: Created ${forest.factory.getTypeCount()} tree types`); console.log(` for ${forest.trees.length} trees (memory efficient!)`); console.log(` Memory saved: ~${Math.floor((forest.trees.length - forest.factory.getTypeCount()) * 100 / forest.trees.length)}%`);}
main();#include <iostream>#include <string>#include <unordered_map>#include <vector>#include <memory>
// Step 1: Define Flyweightclass TreeType {private: std::string name; std::string texture; std::string model; double height;
public: TreeType(const std::string& name, const std::string& texture, const std::string& model, double height) : name(name), texture(texture), model(model), height(height) {}
void render(int x, int y) const { // Render using intrinsic state + extrinsic parameters std::cout << "Rendering " << name << " tree at (" << x << ", " << y << ") with texture " << texture << ", model " << model << ", height " << height << "m" << std::endl; }};
// Step 2: Create Flyweight Factoryclass TreeTypeFactory {private: std::unordered_map<std::string, std::shared_ptr<TreeType>> treeTypes;
public: std::shared_ptr<TreeType> getTreeType(const std::string& name, const std::string& texture, const std::string& model, double height) { // Get or create tree type flyweight std::string key = name + "_" + texture + "_" + model + "_" + std::to_string(height);
// Return existing tree type if it exists if (treeTypes.find(key) == treeTypes.end()) { std::cout << "Creating new tree type: " << name << std::endl; treeTypes[key] = std::make_shared<TreeType>(name, texture, model, height); } else { std::cout << "Reusing tree type: " << name << std::endl; }
return treeTypes[key]; }
int getTypeCount() const { return treeTypes.size(); }};
// Step 3: Client - uses flyweights with extrinsic stateclass Tree {private: int x; // Extrinsic state - unique per tree int y; // Extrinsic state - unique per tree std::shared_ptr<TreeType> treeType; // Reference to flyweight - shared
public: Tree(int x, int y, std::shared_ptr<TreeType> treeType) : x(x), y(y), treeType(treeType) {}
void render() const { // Render tree using flyweight treeType->render(x, y); }};
class Forest {public: TreeTypeFactory factory; std::vector<Tree> trees;
void plantTree(int x, int y, const std::string& name, const std::string& texture, const std::string& model, double height) { // Plant a tree - reuses tree type if possible // Get flyweight (shared intrinsic state) auto treeType = factory.getTreeType(name, texture, model, height);
// Create tree with extrinsic state trees.emplace_back(x, y, treeType); }
void render() const { // Render all trees std::cout << "\nRendering forest with " << trees.size() << " trees:\n" << std::endl; for (const auto& tree : trees) { tree.render(); } }};
// Usage - Memory efficient!int main() { Forest 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();
std::cout << "\n✅ Flyweight Pattern: Created " << forest.factory.getTypeCount() << " tree types" << std::endl; std::cout << " for " << forest.trees.size() << " trees (memory efficient!)" << std::endl; std::cout << " Memory saved: ~" << ((forest.trees.size() - forest.factory.getTypeCount()) * 100 / forest.trees.size()) << "%" << std::endl;
return 0;}using System;using System.Collections.Generic;
// Step 1: Define Flyweightpublic class TreeType{ /** Flyweight - stores intrinsic state (shared) */ private string name; private string texture; private string model; private double height;
public TreeType(string name, string texture, string model, double height) { // Intrinsic state - shared among many trees this.name = name; this.texture = texture; this.model = model; this.height = height; }
public void Render(int x, int y) { // Render using intrinsic state + extrinsic parameters Console.WriteLine($"Rendering {name} tree at ({x}, {y}) with texture {texture}, model {model}, height {height}m"); }}
// Step 2: Create Flyweight Factorypublic class TreeTypeFactory{ /** Flyweight Factory - creates and manages tree types */ private Dictionary<string, TreeType> treeTypes = new Dictionary<string, TreeType>();
public TreeType GetTreeType(string name, string texture, string model, double height) { // Get or create tree type flyweight string key = $"{name}_{texture}_{model}_{height}";
// Return existing tree type if it exists if (!treeTypes.ContainsKey(key)) { Console.WriteLine($"Creating new tree type: {name}"); treeTypes[key] = new TreeType(name, texture, model, height); } else { Console.WriteLine($"Reusing tree type: {name}"); }
return treeTypes[key]; }
public int GetTypeCount() { return treeTypes.Count; }}
// Step 3: Client - uses flyweights with extrinsic statepublic class 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); }}
public class Forest{ /** Forest - manages many trees efficiently */ public TreeTypeFactory factory; public List<Tree> trees;
public Forest() { this.factory = new TreeTypeFactory(); this.trees = new List<Tree>(); }
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 Console.WriteLine($"\nRendering forest with {trees.Count} trees:\n"); foreach (var tree in trees) { tree.Render(); } }}
// Usage - Memory efficient!class Program{ static void Main() { 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();
Console.WriteLine($"\n✅ Flyweight Pattern: Created {forest.factory.GetTypeCount()} tree types"); Console.WriteLine($" for {forest.trees.Count} trees (memory efficient!)"); Console.WriteLine($" Memory saved: ~{(forest.trees.Count - forest.factory.GetTypeCount()) * 100 / forest.trees.Count}%"); }}package main
import "fmt"
// Step 1: Flyweight (TreeType) - intrinsic statetype TreeType struct { name string texture string model string height float64}
func (t *TreeType) Render(x, y int) { fmt.Printf("Rendering %s tree at (%d, %d) texture=%s height=%.1fm\n", t.name, x, y, t.texture, t.height)}
// Step 2: Factorytype TreeTypeFactory struct { treeTypes map[string]*TreeType}
func NewTreeTypeFactory() *TreeTypeFactory { return &TreeTypeFactory{treeTypes: make(map[string]*TreeType)}}
func (f *TreeTypeFactory) GetTreeType(name, texture, model string, height float64) *TreeType { key := fmt.Sprintf("%s_%s_%s_%.1f", name, texture, model, height) if _, ok := f.treeTypes[key]; !ok { fmt.Printf("Creating new tree type: %s\n", name) f.treeTypes[key] = &TreeType{name: name, texture: texture, model: model, height: height} } else { fmt.Printf("Reusing tree type: %s\n", name) } return f.treeTypes[key]}
func (f *TreeTypeFactory) TypeCount() int { return len(f.treeTypes) }
// Step 3: Tree - extrinsic state + flyweight reftype Tree struct { x, y int treeType *TreeType}
func (t *Tree) Render() { t.treeType.Render(t.x, t.y) }
// Step 4: Foresttype Forest struct { factory *TreeTypeFactory trees []*Tree}
func NewForest() *Forest { return &Forest{factory: NewTreeTypeFactory()} }
func (f *Forest) PlantTree(x, y int, name, texture, model string, height float64) { tt := f.factory.GetTreeType(name, texture, model, height) f.trees = append(f.trees, &Tree{x: x, y: y, treeType: tt})}
func (f *Forest) Render() { fmt.Printf("\nRendering forest with %d trees:\n\n", len(f.trees)) for _, t := range f.trees { t.Render() }}
func main() { forest := NewForest() for i := 0; i < 1000; i++ { forest.PlantTree(i*10, i*5, "Oak", "oak_texture.png", "oak_model.obj", 5.0) } for i := 0; i < 500; i++ { forest.PlantTree(i*8, i*6, "Pine", "pine_texture.png", "pine_model.obj", 8.0) } forest.Render() fmt.Printf("\n✅ Flyweight Pattern: Created %d tree types\n", forest.factory.TypeCount()) fmt.Printf(" for %d trees (memory efficient!)\n", len(forest.trees)) fmt.Printf(" Memory saved: ~%d%%\n", (len(forest.trees)-forest.factory.TypeCount())*100/len(forest.trees))}// The Solution: Flyweight Patternuse std::rc::Rc;struct TreeType { name: String, color: String,}struct Tree { x: i32, y: i32, tree_type: Rc<TreeType>,}fn main() { let oak = Rc::new(TreeType { name: "Oak".into(), color: "Green".into(), }); let tree = Tree { x: 10, y: 20, tree_type: Rc::clone(&oak), }; println!("{}", tree.tree_type.name);}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; }}// Standard Flyweight - intrinsic in object, extrinsic as parametersclass Flyweight { private intrinsic: string; // Stored in object
constructor(intrinsic: string) { this.intrinsic = intrinsic; }
operation(extrinsic: string): string { // Extrinsic passed as parameter return `${this.intrinsic} + ${extrinsic}`; }}// Standard Flyweight - intrinsic in object, extrinsic as parametersclass Flyweight {private: std::string intrinsic; // Stored in object
public: Flyweight(const std::string& intrinsic) : intrinsic(intrinsic) {}
std::string operation(const std::string& extrinsic) { // Extrinsic passed as parameter return 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}"; }}package main
// Standard Flyweight - intrinsic in struct, extrinsic as parameterstype StandardFlyweight struct { intrinsic string // Stored in struct}
func NewStandardFlyweight(intrinsic string) *StandardFlyweight { return &StandardFlyweight{intrinsic: intrinsic}}
func (f *StandardFlyweight) Operation(extrinsic string) string { // Extrinsic passed as parameter return f.intrinsic + " + " + extrinsic}// 1. Intrinsic/Extrinsic Separation Standarduse std::rc::Rc;struct TreeType { name: String, color: String,}struct Tree { x: i32, y: i32, tree_type: Rc<TreeType>,}fn main() { let oak = Rc::new(TreeType { name: "Oak".into(), color: "Green".into(), }); let tree = Tree { x: 10, y: 20, tree_type: Rc::clone(&oak), }; println!("{}", tree.tree_type.name);}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(); }}// Context-based Flyweight - extrinsic in context objectclass Context { private extrinsic: string;
constructor(extrinsic: string) { this.extrinsic = extrinsic; }
getExtrinsic(): string { return this.extrinsic; }}
class Flyweight { private intrinsic: string;
constructor(intrinsic: string) { this.intrinsic = intrinsic; }
operation(context: Context): string { return `${this.intrinsic} + ${context.getExtrinsic()}`; }}// Context-based Flyweight - extrinsic in context objectclass Context {private: std::string extrinsic;
public: Context(const std::string& extrinsic) : extrinsic(extrinsic) {}
std::string getExtrinsic() const { return extrinsic; }};
class Flyweight {private: std::string intrinsic;
public: Flyweight(const std::string& intrinsic) : intrinsic(intrinsic) {}
std::string operation(const Context& context) { return intrinsic + " + " + context.getExtrinsic(); }};// 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()}"; }}package main
// Context-based Flyweight - extrinsic in context structtype FlyContext struct { extrinsic string}
func (c *FlyContext) GetExtrinsic() string { return c.extrinsic }
type ContextFlyweight struct { intrinsic string}
func NewContextFlyweight(intrinsic string) *ContextFlyweight { return &ContextFlyweight{intrinsic: intrinsic}}
func (f *ContextFlyweight) Operation(ctx *FlyContext) string { return f.intrinsic + " + " + ctx.GetExtrinsic()}// 2. Context Objectuse std::rc::Rc;struct TreeType { name: String, color: String,}struct Tree { x: i32, y: i32, tree_type: Rc<TreeType>,}fn main() { let oak = Rc::new(TreeType { name: "Oak".into(), color: "Green".into(), }); let tree = Tree { x: 10, y: 20, tree_type: Rc::clone(&oak), }; println!("{}", tree.tree_type.name);}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; }}// ❌ Bad: Not separating intrinsic and extrinsic stateclass BadFlyweight { private intrinsic: string; private extrinsic: string; // Bad: Storing both!
constructor(intrinsic: string, extrinsic: string) { this.intrinsic = intrinsic; this.extrinsic = extrinsic; // Bad: Should be passed as parameter }}
// ✅ Good: Separate intrinsic and extrinsicclass GoodFlyweight { private intrinsic: string; // Good: Only intrinsic stored
constructor(intrinsic: string) { this.intrinsic = intrinsic; }
operation(extrinsic: string): string { // Good: Extrinsic passed as parameter return `${this.intrinsic} + ${extrinsic}`; }}// ❌ Bad: Not separating intrinsic and extrinsic stateclass BadFlyweight {private: std::string intrinsic; std::string extrinsic; // Bad: Storing both!
public: BadFlyweight(const std::string& intrinsic, const std::string& extrinsic) : intrinsic(intrinsic), extrinsic(extrinsic) {} // Bad: Should be passed as parameter};
// ✅ Good: Separate intrinsic and extrinsicclass GoodFlyweight {private: std::string intrinsic; // Good: Only intrinsic stored
public: GoodFlyweight(const std::string& intrinsic) : intrinsic(intrinsic) {}
std::string operation(const std::string& extrinsic) { // Good: Extrinsic passed as parameter return intrinsic + " + " + extrinsic; }};// ❌ Bad: Not separating intrinsic and extrinsic stateclass BadFlyweight{ private string intrinsic; private string extrinsic; // Bad: Storing both!
public BadFlyweight(string intrinsic, string extrinsic) { this.intrinsic = intrinsic; this.extrinsic = extrinsic; // Bad: Should be passed as parameter }}
// ✅ Good: Separate intrinsic and extrinsicclass GoodFlyweight{ private string intrinsic; // Good: Only intrinsic stored
public GoodFlyweight(string intrinsic) { this.intrinsic = intrinsic; }
public string Operation(string extrinsic) // Good: Extrinsic passed as parameter { return $"{intrinsic} + {extrinsic}"; }}package main
// ❌ Bad: Storing both intrinsic and extrinsic in flyweighttype BadFlyweight struct { intrinsic string extrinsic string // Bad: Should be passed as parameter!}
// ✅ Good: Only intrinsic in flyweighttype GoodFlyweightSep struct { intrinsic string // Good: Only intrinsic stored}
func (f *GoodFlyweightSep) Operation(extrinsic string) string { // Good: Extrinsic passed as parameter return f.intrinsic + " + " + extrinsic}// Mistake 1: Not Separating Intrinsic and Extrinsic Statestruct Tree { x: i32, y: i32, name: String, color: String, texture: Vec<u8>,}fn plant_forest() -> Vec<Tree> { (0..1000) .map(|i| Tree { x: i, y: i, name: "Oak".into(), color: "Green".into(), texture: vec![0; 1024], }) .collect()}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 cached// ❌ Bad: Creating flyweights directlyconst flyweight1 = new Flyweight("A"); // Bad: No sharing!const flyweight2 = new Flyweight("A"); // Bad: Creates duplicate!
// ✅ Good: Using factoryconst factory = new FlyweightFactory();const flyweight1 = factory.getFlyweight("A"); // Good: Creates and cachesconst flyweight2 = factory.getFlyweight("A"); // Good: Reuses cached// ❌ Bad: Creating flyweights directlyFlyweight flyweight1("A"); // Bad: No sharing!Flyweight flyweight2("A"); // Bad: Creates duplicate!
// ✅ Good: Using factoryFlyweightFactory factory;auto flyweight1 = factory.getFlyweight("A"); // Good: Creates and cachesauto flyweight2 = factory.getFlyweight("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 cachedpackage main
type SimpleFlyweight struct{ key string }
type SimpleFlyweightFactory struct { cache map[string]*SimpleFlyweight}
func NewSimpleFlyweightFactory() *SimpleFlyweightFactory { return &SimpleFlyweightFactory{cache: make(map[string]*SimpleFlyweight)}}
func (f *SimpleFlyweightFactory) GetFlyweight(key string) *SimpleFlyweight { if _, ok := f.cache[key]; !ok { f.cache[key] = &SimpleFlyweight{key: key} } return f.cache[key]}
// ❌ Bad: Creating flyweights directly// fw1 := &SimpleFlyweight{key: "A"} // No sharing!// fw2 := &SimpleFlyweight{key: "A"} // Creates duplicate!
// ✅ Good: Using factory// factory := NewSimpleFlyweightFactory()// fw1 := factory.GetFlyweight("A") // Creates and caches// fw2 := factory.GetFlyweight("A") // Reuses cached// Mistake 2: Not Using Factorystruct Tree { x: i32, y: i32, name: String, color: String, texture: Vec<u8>,}fn plant_forest() -> Vec<Tree> { (0..1000) .map(|i| Tree { x: i, y: i, name: "Oak".into(), color: "Green".into(), texture: vec![0; 1024], }) .collect()}Mistake 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 + ")"); }}// ❌ Bad: Storing unique data in intrinsic stateclass BadFlyweight { private character: string; private x: number; // Bad: Should be extrinsic! private y: number; // Bad: Should be extrinsic!
constructor(character: string, x: number, y: number) { this.character = character; this.x = x; // Bad: Unique per object! this.y = y; // Bad: Unique per object! }}
// ✅ Good: Only shared data in intrinsic stateclass GoodFlyweight { private character: string; // Good: Only shared data
constructor(character: string) { this.character = character; }
render(x: number, y: number): void { // Good: Unique data as parameters console.log(`${this.character} at (${x}, ${y})`); }}// ❌ Bad: Storing unique data in intrinsic stateclass BadFlyweight {private: char character; int x; // Bad: Should be extrinsic! int y; // Bad: Should be extrinsic!
public: BadFlyweight(char character, int x, int y) : character(character), x(x), y(y) {} // Bad: Unique per object!};
// ✅ Good: Only shared data in intrinsic stateclass GoodFlyweight {private: char character; // Good: Only shared data
public: GoodFlyweight(char character) : character(character) {}
void render(int x, int y) { // Good: Unique data as parameters std::cout << character << " at (" << x << ", " << y << ")" << std::endl; }};// ❌ Bad: Storing unique data in intrinsic stateclass BadFlyweight{ private char character; private int x; // Bad: Should be extrinsic! private int y; // Bad: Should be extrinsic!
public BadFlyweight(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 GoodFlyweight{ private char character; // Good: Only shared data
public GoodFlyweight(char character) { this.character = character; }
public void Render(int x, int y) // Good: Unique data as parameters { Console.WriteLine($"{character} at ({x}, {y})"); }}package main
import "fmt"
// ❌ Bad: Storing unique data in intrinsic statetype BadFlyweightEx struct { character rune x, y int // Bad: Should be extrinsic!}
// ✅ Good: Only shared data in intrinsic statetype GoodFlyweightEx struct { character rune // Good: Only shared data}
func (f *GoodFlyweightEx) Render(x, y int) { // Good: Unique data as parameters fmt.Printf("%c at (%d, %d)\n", f.character, x, y)}// Mistake 3: Storing Too Much in Intrinsic Statestruct Tree { x: i32, y: i32, name: String, color: String, texture: Vec<u8>,}fn plant_forest() -> Vec<Tree> { (0..1000) .map(|i| Tree { x: i, y: i, name: "Oak".into(), color: "Green".into(), texture: vec![0; 1024], }) .collect()}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}" # Uniqueclass Flyweight { private String intrinsic; // Shared Flyweight(String intrinsic) { this.intrinsic = intrinsic; } String operation(String extrinsic) { return intrinsic + " + " + extrinsic; // Unique }}class Flyweight { constructor(private intrinsic: string) {} // Shared operation(extrinsic: string): string { return `${this.intrinsic} + ${extrinsic}`; // Unique }}class Flyweight { std::string intrinsic; // Sharedpublic: Flyweight(const std::string& i) : intrinsic(i) {} std::string operation(const std::string& extrinsic) { return intrinsic + " + " + extrinsic; // Unique }};class Flyweight { private string intrinsic; // Shared public Flyweight(string intrinsic) => this.intrinsic = intrinsic; public string Operation(string extrinsic) => intrinsic + " + " + extrinsic; // Unique}type Flyweight struct{ intrinsic string } // Sharedfunc NewFlyweight(i string) *Flyweight { return &Flyweight{intrinsic: i} }func (f *Flyweight) Operation(extrinsic string) string { return f.intrinsic + " + " + extrinsic // Unique}// Simple Exampleuse std::rc::Rc;struct TreeType { name: String, color: String,}struct Tree { x: i32, y: i32, tree_type: Rc<TreeType>,}fn main() { let oak = Rc::new(TreeType { name: "Oak".into(), color: "Green".into(), }); let tree = Tree { x: 10, y: 20, tree_type: Rc::clone(&oak), }; println!("{}", tree.tree_type.name);}When 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]class Flyweight { private String intrinsic; Flyweight(String intrinsic) { this.intrinsic = intrinsic; } String operation(String extrinsic) { return intrinsic + " + " + extrinsic; }}
class Factory { private Map<String, Flyweight> cache = new HashMap<>(); Flyweight getFlyweight(String key) { cache.putIfAbsent(key, new Flyweight(key)); return cache.get(key); }}class Flyweight { constructor(private intrinsic: string) {} operation(extrinsic: string): string { return `${this.intrinsic} + ${extrinsic}`; }}
class Factory { private cache: Map<string, Flyweight> = new Map(); getFlyweight(key: string): Flyweight { if (!this.cache.has(key)) this.cache.set(key, new Flyweight(key)); return this.cache.get(key)!; }}class Flyweight { std::string intrinsic;public: Flyweight(const std::string& i) : intrinsic(i) {} std::string operation(const std::string& e) { return intrinsic + " + " + e; }};
class Factory { std::map<std::string, Flyweight*> cache;public: Flyweight* getFlyweight(const std::string& key) { if (cache.find(key) == cache.end()) cache[key] = new Flyweight(key); return cache[key]; }};class Flyweight { private string intrinsic; public Flyweight(string intrinsic) => this.intrinsic = intrinsic; public string Operation(string extrinsic) => intrinsic + " + " + extrinsic;}
class Factory { private Dictionary<string, Flyweight> cache = new(); public Flyweight GetFlyweight(string key) { if (!cache.ContainsKey(key)) cache[key] = new Flyweight(key); return cache[key]; }}type FlyweightRev struct{ intrinsic string }func (f *FlyweightRev) Operation(extrinsic string) string { return f.intrinsic + " + " + extrinsic }
type FactoryRev struct{ cache map[string]*FlyweightRev }func NewFactoryRev() *FactoryRev { return &FactoryRev{cache: make(map[string]*FlyweightRev)} }func (f *FactoryRev) GetFlyweight(key string) *FlyweightRev { if _, ok := f.cache[key]; !ok { f.cache[key] = &FlyweightRev{intrinsic: key} } return f.cache[key]}// Common Pattern Structureuse std::rc::Rc;struct TreeType { name: String, color: String,}struct Tree { x: i32, y: i32, tree_type: Rc<TreeType>,}fn main() { let oak = Rc::new(TreeType { name: "Oak".into(), color: "Green".into(), }); let tree = Tree { x: 10, y: 20, tree_type: Rc::clone(&oak), }; println!("{}", tree.tree_type.name);}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! 🪶