9.6. Flyweight
EN: Flyweight
PL: Pyłek
Type: object
The Flyweight design pattern is a structural design pattern that allows you to 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.
Here's a simple example of the Flyweight pattern in Python:
>>> class Flyweight:
... def __init__(self, shared_state: str) -> None:
... self._shared_state = shared_state
...
... def operation(self, unique_state: str) -> None:
... print(f"Flyweight: Displaying shared ({self._shared_state}) and unique ({unique_state}) state.")
...
>>> class FlyweightFactory:
... _flyweights = {}
...
... def get_flyweight(self, shared_state: str) -> Flyweight:
... if not self._flyweights.get(shared_state):
... self._flyweights[shared_state] = Flyweight(shared_state)
... return self._flyweights[shared_state]
...
>>> factory = FlyweightFactory()
>>> flyweight = factory.get_flyweight("shared state")
>>> flyweight.operation("unique state")
Flyweight: Displaying shared (shared state) and unique (unique state) state.
In this example, Flyweight is a class whose instances with the same state can be used interchangeably. FlyweightFactory is a class that creates and manages the Flyweight objects. It ensures that flyweights are shared correctly. When a client requests a flyweight, the FlyweightFactory objects supply an existing instance or create a new one, if it doesn't exist yet.
9.6.1. Pattern
In applications with large number of objects
Objects take significant amount of memory
Reduce memory consumed by objects
9.6.2. Problem
Imagine mapping application, such as: Open Street Maps, Google Maps, Yelp, Trip Advisor etc.
There are thousands points of interests such as Cafe, Shops, Restaurants, School etc.
Icons can take a lot of memory, times number of points on the map
It might crash with out of memory error (especially on mobile devices)
from dataclasses import dataclass
from enum import Enum
class PointType(Enum):
HOSPITAL = 1
CAFE = 2
RESTAURANT = 3
@dataclass
class Point:
x: int # 28 bytes
y: int # 28 bytes
type: PointType # 1064 bytes
icon: bytearray # empty: 56 bytes, but with PNG icon: 20 KB
def draw(self) -> None:
print(f'{self.type} at ({self.x}, {self.y})')
class PointService:
def get_points(self) -> list[Point]:
points: list[Point] = list()
point: Point = Point(1, 2, PointType.CAFE, None)
points.append(point)
return points
if __name__ == '__main__':
service = PointService()
for point in service.get_points():
point.draw()
# PointType.CAFE at (1, 2)
9.6.3. Solution
Separate the data you want to share from other data
Pattern will create a dict with point type and its icon
It will reuse icon for each type
So it will prevent from storing duplicated data in memory
from dataclasses import dataclass, field
from enum import Enum
from typing import Final
class PointType(Enum):
HOSPITAL = 1
CAFE = 2
RESTAURANT = 3
@dataclass
class PointIcon:
type: Final[PointType] # 1064 bytes
icon: Final[bytearray] # empty: 56 bytes, but with PNG icon: 20 KB
def get_type(self):
return self.type
@dataclass
class PointIconFactory:
icons: dict[PointType, PointIcon] = field(default_factory=dict)
def get_point_icon(self, type: PointType) -> PointIcon:
if not self.icons.get(type):
self.icons[type] = PointIcon(type, None) # Here read icon from filesystem
return self.icons.get(type)
@dataclass
class Point:
x: int # 28 bytes
y: int # 28 bytes
icon: PointIcon
def draw(self) -> None:
print(f'{self.icon.get_type()} at ({self.x}, {self.y})')
@dataclass
class PointService:
icon_factory: PointIconFactory
def get_points(self) -> list[Point]:
points: list[Point] = list()
point: Point = Point(1, 2, self.icon_factory.get_point_icon(PointType.CAFE))
points.append(point)
return points
if __name__ == '__main__':
service = PointService(PointIconFactory())
for point in service.get_points():
point.draw()
# PointType.CAFE at (1, 2)