Flyweight Design Pattern (Structural)
Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of state between multiple objects instead of keeping all of the data in each object.
Problem
Section titled βProblemβImagine you are building a video game where thousands of particles (like bullets or sparks) are rendered on the screen at once. Each particle contains specific data: its coordinates, vector, speed, and a large sprite image.
While coordinates and vectors change constantly, the sprite and color are often the same for all particles of a certain type. If you store the image data inside every single particle object, the game will quickly run out of memory and crash.
Solution
Section titled βSolutionβThe Flyweight pattern suggests that you stop storing the intrinsic state (data that is constant and shared) inside each object. Instead, you move this state into a separate βFlyweightβ object that multiple original objects can reference.
The extrinsic state (data that changes, like coordinates) stays with the original object (the βcontextβ). This allows you to create millions of context objects that all point to just a few flyweight objects containing the heavy data.
Intrinsic vs. Extrinsic State
Section titled βIntrinsic vs. Extrinsic Stateβ- Intrinsic State: Lives within the flyweight. It is constant and shareable (e.g., a treeβs texture and color).
- Extrinsic State: Lives outside the flyweight. It is unique and changes based on context (e.g., a treeβs specific $(x, y)$ coordinates).
Real-World Analogy
Section titled βReal-World AnalogyβIn a forest, you might see thousands of trees. Each tree has its own location on the ground. However, the βlookβ of a specific species of tree (the shape of the leaves, the texture of the bark) is shared. Instead of every tree having its own unique βdesign plan,β nature uses one genetic blueprint for the species and applies it to different locations.
Structure
Section titled βStructureβ- Flyweight: Contains the portion of the state that can be shared between multiple objects.
- Context: Contains the extrinsic state, unique across all original objects.
- Flyweight Factory: Manages a pool of existing flyweights. It ensures that flyweights are shared and not duplicated.
- Client: Calculates or stores the extrinsic state and passes it to the flyweight methods.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class FlyweightFactory {
-flyweights: Flyweight[]
+getFlyweight(sharedState)
}
class Flyweight {
-intrinsicState
+operation(extrinsicState)
}
class Context {
-extrinsicState
-flyweight: Flyweight
+operation()
}
FlyweightFactory o-- Flyweight
Context o-- Flyweight
Client --> FlyweightFactory
Client --> Context
Pseudocode
Section titled βPseudocodeβ# The Flyweight stores a portion of the state of a tree.# These fields store values that are unique for each# particular tree.class TreeType: field name, color, texture constructor TreeType(name, color, texture) { ... } method draw(canvas, x, y): # 1. Create a bitmap of a given type, color & texture. # 2. Draw the bitmap on the canvas at X and Y.
# Flyweight Factory decides whether to re-use existing# flyweight or create a new object.class TreeFactory: static field treeTypes: set of TreeType static method getTreeType(name, color, texture): type = treeTypes.find(name, color, texture) if (type == null): type = new TreeType(name, color, texture) treeTypes.add(type) return type
# The Context object contains the extrinsic part of the state.class Tree: field x, y field type: TreeType constructor Tree(x, y, type) { ... } method draw(canvas): type.draw(canvas, this.x, this.y)Applicability
Section titled βApplicabilityβ- Memory Constraints: Use the Flyweight pattern only when your program must spawn a huge number of objects which barely fit into available RAM.
- Redundant State: Use it when the objects contain a large amount of duplicate state that can be extracted and shared.
How to Implement
Section titled βHow to Implementβ- Divide the fields of a class that will become a flyweight into two parts: intrinsic (constant) and extrinsic (contextual).
- Leave the intrinsic state in the class, but make it immutable.
- Create a new context class for the extrinsic state. It should contain a reference to the flyweight object.
- Create a factory that manages the pool of flyweights.
- Clients must use the factory to request flyweights instead of instantiating them directly.
Pros and Cons
Section titled βPros and ConsβPros:
- You can save a massive amount of RAM, assuming your program has many similar objects.
Cons:
- You might be trading RAM for CPU cycles if the extrinsic data has to be recalculated every time itβs used.
- The code becomes much more complicated.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- Composite: Flyweight can be used to implement shared leaf nodes in a Composite tree to save RAM.
- Facade: Flyweight explains how to create lots of small objects, while Facade shows how to create a single object that represents an entire subsystem.
- Singleton: Flyweight resembles Singleton if you somehow manage to reduce all shared states of the objects to just one singleton object.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Flyweightpublic class TreeType{ public string Name { get; } public string Color { get; }
public TreeType(string name, string color) { Name = name; Color = color; }
public void Draw(int x, int y) { Console.WriteLine($"Draw {Name} ({Color}) in ({x}, {y})"); }}
// Flyweight Factorypublic class TreeFactory{ private static readonly Dictionary<string, TreeType> _types = new();
public static TreeType GetTreeType(string name, string color) { string key = $"{name}-{color}";
if (!_types.ContainsKey(key)) _types[key] = new TreeType(name, color);
return _types[key]; }}
// Contextpublic class Tree{ private readonly int _x; private readonly int _y; private readonly TreeType _type;
public Tree(int x, int y, TreeType type) { _x = x; _y = y; _type = type; }
public void Draw() => _type.Draw(_x, _y);}
// Usageclass Program{ static void Main() { var trees = new List<Tree>();
for (int i = 0; i < 5; i++) { var type = TreeFactory.GetTreeType("Oak", "Green"); trees.Add(new Tree(i * 10, i * 5, type)); }
foreach (var tree in trees) tree.Draw(); }}Code Examples (Python)
Section titled βCode Examples (Python)βimport jsonfrom typing import Dict
class Flyweight(): """ The Flyweight stores a common portion of the state (intrinsic state). """ def __init__(self, shared_state: str) -> None: self._shared_state = shared_state
def operation(self, unique_state: str) -> None: s = json.dumps(self._shared_state) u = json.dumps(unique_state) print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.")
class FlyweightFactory(): """ The Flyweight Factory creates and manages the Flyweight objects. """ _flyweights: Dict[str, Flyweight] = {}
def __init__(self, initial_flyweights: list) -> None: for state in initial_flyweights: self._flyweights[self.get_key(state)] = Flyweight(state)
def get_key(self, state: list) -> str: return "_".join(sorted(state))
def get_flyweight(self, shared_state: list) -> Flyweight: key = self.get_key(shared_state)
if not self._flyweights.get(key): print("FlyweightFactory: Can't find a flyweight, creating new one.") self._flyweights[key] = Flyweight(shared_state) else: print("FlyweightFactory: Reusing existing flyweight.")
return self._flyweights[key]
if __name__ == "__main__": factory = FlyweightFactory([ ["Chevrolet", "Camaro2018", "pink"], ["Mercedes Benz", "C300", "black"], ])
# Reusing existing flyweight flyweight = factory.get_flyweight(["Mercedes Benz", "C300", "black"]) flyweight.operation(["CL234IR", "James Doe"])
# Creating a new flyweight flyweight2 = factory.get_flyweight(["BMW", "M5", "red"]) flyweight2.operation(["NI888OO", "Jane Doe"])Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <string>#include <unordered_map>#include <memory>#include <vector>
// Flyweightclass TreeType {private: std::string name; std::string color;
public: TreeType(const std::string& n, const std::string& c) : name(n), color(c) {}
void Draw(int x, int y) const { std::cout << "Draw " << name << " (" << color << ") in (" << x << ", " << y << ")" << std::endl; }};
// Flyweight Factoryclass TreeFactory {private: static inline std::unordered_map<std::string, std::unique_ptr<TreeType>> types;
public: static TreeType* GetTreeType(const std::string& name, const std::string& color) { std::string key = name + "-" + color;
if (types.find(key) == types.end()) { types[key] = std::make_unique<TreeType>(name, color); }
return types[key].get(); }};
// Contextclass Tree {private: int x; int y; TreeType* type;
public: Tree(int xCoord, int yCoord, TreeType* t) : x(xCoord), y(yCoord), type(t) {}
void Draw() const { type->Draw(x, y); }};
// Usageint main() { std::vector<std::unique_ptr<Tree>> trees;
for (int i = 0; i < 5; ++i) { TreeType* type = TreeFactory::GetTreeType("Oak", "Green"); trees.push_back(std::make_unique<Tree>(i * 10, i * 5, type)); }
for (const auto& tree : trees) { tree->Draw(); }
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Flyweight β `TreeType`public class TreeType {
private final String name; private final String color;
public TreeType(String name, String color) { this.name = name; this.color = color; }
public void draw(int x, int y) { System.out.println("Draw " + name + " (" + color + ") in (" + x + ", " + y + ")"); }}
// Flyweight Factory β `TreeFactory`import java.util.HashMap;import java.util.Map;
public class TreeFactory {
private static final Map<String, TreeType> types = new HashMap<>();
public static TreeType getTreeType(String name, String color) { String key = name + "-" + color;
if (!types.containsKey(key)) { types.put(key, new TreeType(name, color)); }
return types.get(key); }}
// Context β `Tree`public class Tree {
private final int x; private final int y; private final TreeType type;
public Tree(int x, int y, TreeType type) { this.x = x; this.y = y; this.type = type; }
public void draw() { type.draw(x, y); }}
// Usageimport java.util.ArrayList;import java.util.List;
public class Program { public static void main(String[] args) {
List<Tree> trees = new ArrayList<>();
for (int i = 0; i < 5; i++) { TreeType type = TreeFactory.getTreeType("Oak", "Green"); trees.add(new Tree(i * 10, i * 5, type)); }
for (Tree tree : trees) { tree.draw(); } }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Flyweight β intrinsic shared stateclass TreeType { constructor(name, color) { this.name = name; this.color = color; }
draw(x, y) { console.log(`Draw ${this.name} (${this.color}) in (${x}, ${y})`); }}
// Flyweight Factory β ensures reuseclass TreeFactory { static types = {};
static getTreeType(name, color) { const key = `${name}-${color}`;
if (!TreeFactory.types[key]) { TreeFactory.types[key] = new TreeType(name, color); }
return TreeFactory.types[key]; }}
// Context β extrinsic stateclass Tree { constructor(x, y, type) { this.x = x; this.y = y; this.type = type; }
draw() { this.type.draw(this.x, this.y); }}
// Usageconst trees = [];
for (let i = 0; i < 5; i++) { const type = TreeFactory.getTreeType("Oak", "Green"); trees.push(new Tree(i * 10, i * 5, type));}
for (const tree of trees) { tree.draw();}Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Flyweight' ============================Public Class TreeType Public ReadOnly Property Name As String Public ReadOnly Property Color As String
Public Sub New(name As String, color As String) Me.Name = name Me.Color = color End Sub
Public Sub Draw(x As Integer, y As Integer) Console.WriteLine($"Draw {Name} ({Color}) in ({x}, {y})") End SubEnd Class
' ============================' Flyweight Factory' ============================Public Class TreeFactory Private Shared ReadOnly _types As New Dictionary(Of String, TreeType)()
Public Shared Function GetTreeType(name As String, color As String) As TreeType Dim key As String = $"{name}-{color}"
If Not _types.ContainsKey(key) Then _types(key) = New TreeType(name, color) End If
Return _types(key) End FunctionEnd Class
' ============================' Context' ============================Public Class Tree Private ReadOnly _x As Integer Private ReadOnly _y As Integer Private ReadOnly _type As TreeType
Public Sub New(x As Integer, y As Integer, type As TreeType) _x = x _y = y _type = type End Sub
Public Sub Draw() _type.Draw(_x, _y) End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim trees As New List(Of Tree)()
For i As Integer = 0 To 4 Dim type = TreeFactory.GetTreeType("Oak", "Green") trees.Add(New Tree(i * 10, i * 5, type)) Next
For Each tree In trees tree.Draw() Next End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)βuse std::collections::HashMap;use std::sync::OnceLock;
// Flyweight#[derive(Debug)]struct TreeType { name: String, color: String,}
impl TreeType { fn new(name: &str, color: &str) -> Self { Self { name: name.to_string(), color: color.to_string(), } }
fn draw(&self, x: i32, y: i32) { println!("Draw {} ({}) in ({}, {})", self.name, self.color, x, y); }}
// Flyweight Factorystruct TreeFactory;
static TYPES: OnceLock<HashMap<String, TreeType>> = OnceLock::new();
impl TreeFactory { fn get_tree_type(name: &str, color: &str) -> &'static TreeType { let map = TYPES.get_or_init(HashMap::new);
let key = format!("{}-{}", name, color);
// SAFETY: We must mutate the map, so we rebuild it immutably. if !map.contains_key(&key) { let mut new_map = map.clone(); new_map.insert(key.clone(), TreeType::new(name, color)); TYPES.set(new_map).ok(); }
&TYPES.get().unwrap()[&key] }}
// Contextstruct Tree { x: i32, y: i32, tree_type: &'static TreeType,}
impl Tree { fn new(x: i32, y: i32, tree_type: &'static TreeType) -> Self { Self { x, y, tree_type } }
fn draw(&self) { self.tree_type.draw(self.x, self.y); }}
// Usagefn main() { let mut trees: Vec<Tree> = Vec::new();
for i in 0..5 { let t = TreeFactory::get_tree_type("Oak", "Green"); trees.push(Tree::new(i * 10, i * 5, t)); }
for tree in trees { tree.draw(); }}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Flyweightclass TreeType{ public string $name; public string $color;
public function __construct(string $name, string $color) { $this->name = $name; $this->color = $color; }
public function draw(int $x, int $y): void { echo "Draw {$this->name} ({$this->color}) in ({$x}, {$y})\n"; }}
// Flyweight Factoryclass TreeFactory{ /** @var TreeType[] */ private static array $types = [];
public static function getTreeType(string $name, string $color): TreeType { $key = "{$name}-{$color}";
if (!isset(self::$types[$key])) { self::$types[$key] = new TreeType($name, $color); }
return self::$types[$key]; }}
// Contextclass Tree{ private int $x; private int $y; private TreeType $type;
public function __construct(int $x, int $y, TreeType $type) { $this->x = $x; $this->y = $y; $this->type = $type; }
public function draw(): void { $this->type->draw($this->x, $this->y); }}
// Usage$trees = [];
for ($i = 0; $i < 5; $i++) { $type = TreeFactory::getTreeType("Oak", "Green"); $trees[] = new Tree($i * 10, $i * 5, $type);}
foreach ($trees as $tree) { $tree->draw();}Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Flyweighttype TreeType struct { Name string Color string}
func NewTreeType(name, color string) *TreeType { return &TreeType{Name: name, Color: color}}
func (t *TreeType) Draw(x, y int) { fmt.Printf("Draw %s (%s) in (%d, %d)\n", t.Name, t.Color, x, y)}
// Flyweight Factorytype TreeFactory struct { types map[string]*TreeType}
func NewTreeFactory() *TreeFactory { return &TreeFactory{ types: make(map[string]*TreeType), }}
func (f *TreeFactory) GetTreeType(name, color string) *TreeType { key := name + "-" + color
if _, exists := f.types[key]; !exists { f.types[key] = NewTreeType(name, color) }
return f.types[key]}
// Contexttype Tree struct { x, y int typ *TreeType}
func NewTree(x, y int, typ *TreeType) *Tree { return &Tree{x: x, y: y, typ: typ}}
func (t *Tree) Draw() { t.typ.Draw(t.x, t.y)}
// Usagefunc main() { factory := NewTreeFactory() trees := []*Tree{}
for i := 0; i < 5; i++ { typ := factory.GetTreeType("Oak", "Green") trees = append(trees, NewTree(i*10, i*5, typ)) }
for _, tree := range trees { tree.Draw() }}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Flyweightclass TreeTypeattr_reader :name, :color
def initialize(name, color) @name = name @color = colorend
def draw(x, y) puts "Draw #{name} (#{color}) in (#{x}, #{y})"endend
# Flyweight Factoryclass TreeFactory@types = {}
def self.get_tree_type(name, color) key = "#{name}-#{color}" @types[key] ||= TreeType.new(name, color)endend
# Contextclass Treedef initialize(x, y, type) @x = x @y = y @type = typeend
def draw @type.draw(@x, @y)endend
# Usagetrees = []
5.times do |i|type = TreeFactory.get_tree_type("Oak", "Green")trees << Tree.new(i * 10, i * 5, type)end
trees.each(&:draw)Code Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Flyweight############################package TreeType;
sub new { my ($class, $name, $color) = @_; return bless { name => $name, color => $color }, $class;}
sub draw { my ($self, $x, $y) = @_; print "Draw $self->{name} ($self->{color}) in ($x, $y)\n";}
############################# Flyweight Factory############################package TreeFactory;
my %types; # lexical cache, acts like static dictionary
sub get_tree_type { my ($class, $name, $color) = @_; my $key = "$name-$color";
if (!exists $types{$key}) { $types{$key} = TreeType->new($name, $color); }
return $types{$key};}
############################# Context############################package Tree;
sub new { my ($class, $x, $y, $type) = @_; return bless { x => $x, y => $y, type => $type }, $class;}
sub draw { my ($self) = @_; $self->{type}->draw($self->{x}, $self->{y});}
############################# Usage############################package main;
my @trees;
for my $i (0 .. 4) { my $type = TreeFactory->get_tree_type("Oak", "Green"); push @trees, Tree->new($i * 10, $i * 5, $type);}
$_->draw() for @trees;Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Flyweightclass TreeType { name: string; color: string;
constructor(name: string, color: string) { this.name = name; this.color = color; }
draw(x: number, y: number): void { console.log(`Draw ${this.name} (${this.color}) in (${x}, ${y})`); }}
// Flyweight Factoryclass TreeFactory { private static types: Record<string, TreeType> = {};
static getTreeType(name: string, color: string): TreeType { const key = `${name}-${color}`;
if (!TreeFactory.types[key]) { TreeFactory.types[key] = new TreeType(name, color); }
return TreeFactory.types[key]; }}
// Contextclass Tree { private x: number; private y: number; private type: TreeType;
constructor(x: number, y: number, type: TreeType) { this.x = x; this.y = y; this.type = type; }
draw(): void { this.type.draw(this.x, this.y); }}
// Usageconst trees: Tree[] = [];
for (let i = 0; i < 5; i++) { const type = TreeFactory.getTreeType("Oak", "Green"); trees.push(new Tree(i * 10, i * 5, type));}
for (const tree of trees) { tree.draw();}