Flyweight
This pattern is used to minimize memory usage by sharing as much data as possible with other similar objects, rather than keeping common state information in the object itself. This can be seen in characters of a text editor, for example.
TL;DR
The Flyweight pattern is used to minimize RAM expenditure by sharing as much data as possible between objects.
https://refactoring.guru/design-patterns/flyweight
Problem
Suppose you are creating a bullet-storm game. Given that at any moment you might have millions of bullets flying around, your performance starts to degrade very quickly if each bullet itself has to maintain all its properties intrinsically, including those that are always the same for every bullet (such as the sprite and the color).
Solution
The solution is to implement a separate class that stores the extrinsic properties of the objects so that they get reused, rather than recomputed.
Text example
class TextFormat:
"""Flyweight class - stores intrinsic state (font, size, color)"""
def __init__(self, font, size, color):
self.font = font
self.size = size
self.color = color
def display(self, text, position):
"""Operation that uses both intrinsic and extrinsic state"""
print(f"Text: '{text}' at {position} - Font: {self.font}, Size: {self.size}, Color: {self.color}")
class TextFormatFactory:
"""Factory to manage flyweight instances"""
_formats = {}
@classmethod
def get_format(cls, font, size, color):
key = (font, size, color)
if key not in cls._formats:
cls._formats[key] = TextFormat(font, size, color)
print(f"Created new TextFormat: {key}")
return cls._formats[key]
@classmethod
def get_created_formats_count(cls):
return len(cls._formats)
class TextElement:
"""Context class - stores extrinsic state (text content, position)"""
def __init__(self, text, position, font, size, color):
self.text = text # extrinsic state
self.position = position # extrinsic state
self.format = TextFormatFactory.get_format(font, size, color) # flyweight reference
def display(self):
self.format.display(self.text, self.position)
# Example usage
if __name__ == "__main__":
# Create multiple text elements
elements = []
# Many elements with the same formatting
elements.append(TextElement("Hello", (10, 20), "Arial", 12, "black"))
elements.append(TextElement("World", (50, 20), "Arial", 12, "black"))
elements.append(TextElement("Python", (100, 20), "Arial", 12, "black"))
# Some elements with different formatting
elements.append(TextElement("Title", (10, 50), "Arial", 16, "blue"))
elements.append(TextElement("Subtitle", (10, 80), "Arial", 14, "gray"))
elements.append(TextElement("Footer", (10, 200), "Arial", 12, "black"))
print("Displaying all text elements:")
for element in elements:
element.display()
print(f"\nTotal TextFormat objects created: {TextFormatFactory.get_created_formats_count()}")
print(f"Total TextElement objects created: {len(elements)}")
print("Notice how flyweights are reused - only 4 TextFormat objects were created for 6 TextElements!")Bullet example
To do this, we might implement a BulletType class to serve as a flyweight; this class can store the sprite, damage, speed, and sound effect of each bullet.
Next we implement a BulletTypeFactory, a Factory that can manage the bullet types themselves (i.e. for different weapons such as pistols, rifles, etc.). Finally, we implement a Bullet class that stores extrinsic data for each bullet that can vary, such as position, direction of travel, etc.
This means that despite the potential existence of thousands of Bullets, the entire set of Bullets can exist with only a few BulletType instances.
class BulletType:
"""Flyweight class - stores intrinsic state (sprite, damage, speed, sound)"""
def __init__(self, name, sprite, damage, speed, sound_effect):
self.name = name
self.sprite = sprite # image/texture data
self.damage = damage
self.speed = speed
self.sound_effect = sound_effect
def fire(self, position, direction, velocity):
"""Operation that uses both intrinsic and extrinsic state"""
print(f"Firing {self.name} bullet:")
print(f" Position: {position}, Direction: {direction}")
print(f" Speed: {self.speed}, Damage: {self.damage}")
print(f" Playing sound: {self.sound_effect}")
print(f" Rendering sprite: {self.sprite}")
def render(self, position):
"""Render the bullet at a specific position"""
print(f"Rendering {self.sprite} at {position}")
class BulletTypeFactory:
"""Factory to manage bullet type flyweights"""
_bullet_types = {}
@classmethod
def get_bullet_type(cls, name, sprite, damage, speed, sound_effect):
if name not in cls._bullet_types:
cls._bullet_types[name] = BulletType(name, sprite, damage, speed, sound_effect)
print(f"Created new BulletType: {name}")
return cls._bullet_types[name]
@classmethod
def get_types_count(cls):
return len(cls._bullet_types)
class Bullet:
"""Context class - stores extrinsic state (position, direction, velocity)"""
def __init__(self, bullet_type, position, direction, velocity=(0, 0)):
self.bullet_type = bullet_type # flyweight reference
self.position = list(position) # extrinsic state - changes frequently
self.direction = direction # extrinsic state
self.velocity = list(velocity) # extrinsic state - changes during flight
self.active = True
def update(self, delta_time):
"""Update bullet position based on its type's speed"""
if self.active:
# Calculate movement based on flyweight's speed and current direction
speed = self.bullet_type.speed * delta_time
self.position[0] += self.direction[0] * speed
self.position[1] += self.direction[1] * speed
def render(self):
"""Render the bullet using its flyweight"""
if self.active:
self.bullet_type.render(tuple(self.position))
def fire(self):
"""Fire the bullet"""
self.bullet_type.fire(tuple(self.position), self.direction, self.velocity)
class Game:
"""Game context that manages many bullets"""
def __init__(self):
self.bullets = []
# Pre-create bullet types (flyweights)
self.pistol_type = BulletTypeFactory.get_bullet_type(
"Pistol", "pistol_bullet.png", 25, 300, "pistol_shot.wav"
)
self.rifle_type = BulletTypeFactory.get_bullet_type(
"Rifle", "rifle_bullet.png", 50, 500, "rifle_shot.wav"
)
self.shotgun_type = BulletTypeFactory.get_bullet_type(
"Shotgun", "shotgun_pellet.png", 15, 250, "shotgun_blast.wav"
)
def fire_pistol(self, position, direction):
bullet = Bullet(self.pistol_type, position, direction)
bullet.fire()
self.bullets.append(bullet)
def fire_rifle(self, position, direction):
bullet = Bullet(self.rifle_type, position, direction)
bullet.fire()
self.bullets.append(bullet)
def fire_shotgun(self, position, direction):
# Shotgun fires multiple pellets
import random
for i in range(5): # 5 pellets per shot
# Slight random spread for each pellet
spread_x = direction[0] + random.uniform(-0.2, 0.2)
spread_y = direction[1] + random.uniform(-0.2, 0.2)
bullet = Bullet(self.shotgun_type, position, (spread_x, spread_y))
self.bullets.append(bullet)
print(f"Shotgun blast fired 5 pellets!")
def update(self, delta_time):
"""Update all active bullets"""
for bullet in self.bullets:
bullet.update(delta_time)
def render(self):
"""Render all active bullets"""
print("\n--- Rendering all bullets ---")
for bullet in self.bullets:
bullet.render()
def get_stats(self):
return {
'total_bullets': len(self.bullets),
'bullet_types_created': BulletTypeFactory.get_types_count()
}
# Example usage - Simulating a game scenario
if __name__ == "__main__":
game = Game()
print("=== Game Combat Simulation ===\n")
# Player fires various weapons
game.fire_pistol((100, 200), (1, 0)) # Fire right
game.fire_pistol((100, 200), (0, 1)) # Fire up
game.fire_rifle((150, 250), (-1, 0)) # Fire left
game.fire_shotgun((200, 300), (0, -1)) # Fire down (creates 5 bullets)
# Enemy fires back
game.fire_pistol((300, 400), (-1, -1)) # Fire diagonally
game.fire_rifle((350, 450), (-1, 0)) # Fire left
print(f"\n=== Game Stats ===")
stats = game.get_stats()
print(f"Total bullets in game: {stats['total_bullets']}")
print(f"Bullet types created: {stats['bullet_types_created']}")
print(f"\nMemory efficiency: {stats['total_bullets']} bullets share only {stats['bullet_types_created']} flyweight objects!")
print(f"\n=== Update and Render Cycle ===")
game.update(0.016) # ~60 FPS
game.render()
print(f"\n=== Key Benefits ===")
print("✓ Sprite data, damage, speed, and sound effects are shared")
print("✓ Only position, direction, and velocity are stored per bullet")
print("✓ Hundreds of bullets can exist with minimal memory overhead")
print("✓ Easy to add new bullet types without changing existing code")