Introduction
The flyweight pattern is a pattern that helps minimize memory usage by sharing and reusing data. A common example is wordprocessor where each character not only holds the character like ‘A’ but also things like markup and formatting data. Instead of having each element of a document carry all this data, this markup and formatting data is stored using flyweight patterns, to which the elements refer. This method saves a lot of memory.
So, flyweight objects can have:
- Intrinsic state: invariant and shareable, but also context independent (like aformentioned charachter ‘A’). This is what is also called the shared state
- Extrinsic state: that could be the position of the character in the document. This is also known as the unique state
So, what does it look like? Well, a very simple version is this:
Implementation in Python
We will start this pattern by defining a very simply Flyweight class:
class FlyWeight:
_number: int = None
def __init__(self, initial_value: int):
self._number = initial_value
def set_state(self, value: int):
self._number = value
def get_state(self) -> int:
return self._number
To keep things simple I chose an int32 as the state, but this could of course be any object. A nice extension would be to use generics, but I will come to that in a later article.
The Flyweight-structures need to be managed, and that is the task of the FlyweightFactory:
class FlyweightFactory:
_elements: dict[int, FlyWeight] = {}
def get_flyweight(self, number: int) -> FlyWeight:
if self._elements.get(number) is None:
self._elements[number] = FlyWeight(number)
return self._elements[number]
This code is a bit more involved:
- The FlyweightFactory struct has a (hash)map or dictionary, with int32 as keys and Flyweight structures as values.
- The getFlyWeight method does two things: if the key does not exists (that is checked for in the first if statement), then a new Flyweight is made and added to the map
- If it does exist or is just added, it is returned to the caller.
Test the code
Now we can test our code:
if __name__ == "__main__":
factory: FlyweightFactory = FlyweightFactory()
flyweight_a: FlyWeight = factory.get_flyweight(1)
flyweight_a.set_state(11)
flyweight_b: FlyWeight = factory.get_flyweight(2)
flyweight_b.set_state(22)
print(flyweight_a.get_state())
print(flyweight_b.get_state())
Line by line:
- We create a new FlyweightFactory
- We try and get a Flyweight from it.
- We set its intrinsic state
- And we repeats steps 2 and 3 with another Flyweight
- Finally we print the two states.
Conclusion
As you can see the Flyweight pattern is not a difficult pattern to implement and it can be useful in many situations, not only for wordprocessors. You can use it as an intelligent cache, or in some cases even as a replacement for a remote proxy (or in combination with such a pattern)
There are two extensions which I would like to add to this pattern, but I will do that in later articles:
- Make the flyweightfactory threadsafe.
- Use generics to make the whole pattern more flexible.