Flyweight Design Pattern in Python
Manage efficiently large numbers of similar objects to avoid RAM
This story is part of the “Design Patterns” series. You can find the other stories of this series here:
You can also find all the code used through this series on GitHub.
The Flyweight pattern is a design pattern used in computer programming to efficiently manage large numbers of similar objects. The idea is to use shared, immutable objects to represent data, and to use separate, mutable objects to hold the state that varies between instances.
Problems the Flyweight Pattern can Solve
Imagine we’re building a game where there are thousands of trees in the game world. Each tree has a unique location, size, and type, but they all share the same texture.
The naive solution is the following:
import time
from unittest.mock import Mock
import random
Image = Mock()
Image.open = lambda image: time.sleep(0.00001)
class Tree:
def __init__(self, x, y, size, type, texture):
self.x = x
self.y = y
self.size = size
self.type = type
self.texture = texture
if __name__ == "__main__":
time_start = time.time()
trees = []
for i in range(100):
x = random.randint(0, 1000)
y = random.randint(0, 1000)
size = random.randint(1, 10)
type = random.choice(['Oak', 'Maple', 'Pine'])
texture = Image.open('tree_texture.png')
trees.append(Tree(x, y, size, type, texture))
time_end = time.time()
print(f"Time taken: {time_end - time_start}")Here, all the trees share the same texture but for each tree, the texture is loaded. It would be wiser to load the texture once and pass it to all trees, wouldn’t it?
You can try to run this code. With my computer, it takes an average of 1.55 seconds to run. And it’s just to load 100 trees, imagine a map with thousands of trees.
Solution
This problem is easily solvable with the Flyweight pattern.
First, we need to identify what stays the same from one tree to the other. Here, it’s just the texture.
So we need to create another class for the shared state, which is just the texture.
import time
from unittest.mock import Mock
import random
Image = Mock()
Image.open = lambda image: time.sleep(0.00001)
class TreeTexture:
def __init__(self, file_path):
self.texture = Image.open(file_path)
# Create a single instance of the shared texture
tree_texture = TreeTexture('tree_texture.png')
class Tree:
def __init__(self, x, y, size, type):
self.x = x
self.y = y
self.size = size
self.type = type
self.texture = tree_texture
if __name__ == "__main__":
time_start = time.time()
trees = []
for i in range(100):
x = random.randint(0, 1000)
y = random.randint(0, 1000)
size = random.randint(1, 10)
type = random.choice(['Oak', 'Maple', 'Pine'])
trees.append(Tree(x, y, size, type))
time_end = time.time()
print(f"Time taken: {time_end - time_start}")Here, we’ve created a separate class TreeTexture which holds the shared texture as an instance variable, and we've instantiated it only once.
We've also updated the Tree class to store a reference to the TreeTexture instance instead of the texture itself, which is the same across all the tree.
This way we are not creating multiple copies of the same texture, saving memory and improving performance.
Applications
- When a large number of similar objects need to be created and used
- When the creation of these objects is expensive in terms of memory and performance
- When the state of these objects can be divided into intrinsic and extrinsic state
Advantages
- Reduces memory usage by sharing immutable objects
- Improves performance by reducing the number of objects created
- Simplifies code by reducing the number of objects to manage
Disadvantages
- Can make it harder to add new types of objects or change the state of existing objects
- It may not be appropriate for small numbers of objects or for objects that are not structurally similar
Final Note
The Flyweight pattern is useful for managing large numbers of similar objects in a way that reduces memory usage and improves performance.
I’ve rarely used it as I never had to deal with huge amounts of data causing RAM issues, but maybe one day I’ll need it. Anyways it’s useful to know it.
To explore the other stories of this story, click below!
To explore more of my Python stories, click here! You can also access all my content by checking this page.
If you want to be notified every time I publish a new story, subscribe to me via email by clicking here!
If you’re not subscribed to medium yet and wish to support me or get access to all my stories, you can use my link:
