Easy Patterns in Python: The Flyweight Pattern

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:

  1. We create a new FlyweightFactory
  2. We try and get a Flyweight from it.
  3. We set its intrinsic state
  4. And we repeats steps 2 and 3 with another Flyweight
  5. 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:

  1. Make the flyweightfactory threadsafe.
  2. Use generics to make the whole pattern more flexible.

Leave a Reply

Your email address will not be published. Required fields are marked *