Flyweight Design Pattern

Think of our browsers optimizing the network performance for –
the heavy resources like images or common resources like css or js files!

It downloads and caches them, so that multiple pages can re-use these files.

Similarly, we use the Flyweight pattern to cache and share the common parts to optimize the memory footprint.

Its a structural design pattern useful for optimizing the memory footprint by sharing the repetitive object instances in a fine grained design.

Which instances can we share ?

When we divide our classes into fine grained objects, it makes our design simple and flexible. But, higher number of objects adds up the memory overhead for each separate instance. But, luckily when we break down the monolithic structures, we get lots of invariant (never changing) components, such as :

  1. Immutable(i.e. the ones those don’t change state once created) data objects.
    • a product object to provide data such as its brand, category, price.
    • a glyph object representing a font
  2. Stateless (i.e. the ones those don’t store client specific data) processing objects. For example :
    • loanProcessor.process(loan) – a loan processor to process loans
    • Graphical objects in games. The coordinates may vary but, we can re-use the drawing object instance.
      • e.g. angryBird.draw(position, color,…)

Hence, instead of creating different instances, we can cache and share just one instance of these invariant types. Because of such sharing, the fine-grained designs may even reduce the memory foot print compared to its coarse grained versions.

When to share?

Flyweight is a simple but, very useful pattern for building lightweight applications. Sharing the invariant object instances, may not only minimize the memory usage, but also save us the initialization cost. Especially, its very significant in the following scenarios :

  1. First, when we need the same instance repeatedly in large numbers. For example :
    • The fonts in a word processor- 26 letters repeated so many times in a document for displaying characters.
    • Shared configuration objects in an application
    • A small set of product items, in a restaurant application, which we fetch repeatedly for our billing purpose.
  2. Secondly, when concurrent clients or repeated requests, need the same components again and again. For example :
    • A set of controller, service, repository and such fine-grained objects which we need for processing our service requests.
      • Singleton beans in a Spring framework is a perfect example in this regard.
    • A stateless loan processor or validator instance in a loan processing application.

 

How does the pattern work?

Example –1

Lets say we have a highly customizable AngryBird component as shown and we need 1000s of these of the given pattern.

With the given structure we will need 1000 AngryBirds. But, can we optimize our data?

How about separating the pattern from its position as shown?

In such a case, we can separately define our patterns and save them as our invariants(fixed patterns) we want to re-use. When we need only 3 separate patterns, it will reduce 1000 repetition in the coarse grained structure to just 3 invariant instances.

With the reference to these patterns in place, our new Angry Bird is much lighter!

Off course we can divide our AngryBird, into still more granular levels depending on the flexibility of choice we need. But, sharing invariant granular instances can save us lots of unnecessary new instances. For instance, the java allows us to use cached instances for granular invariant objects like :

  • Only two Boolean instances for our entire application, if we use Boolean.getValue(true/false), Boolean.TRUE/FALSE.
  • Similarly, we can use cached Characters instances using Character.valeOf(char) and cached Integer instances using Integer.valueOf(int)

Example –2

When we look at the example in our Factory Design Pattern, we are sharing a single instance of the loan processors for each new request. But, this won’t have been possible, if these processors were part of the loan object itself. By keeping the processor separate we have carved out the invariant processing feature from the loan.

Again, because of the factory we are able to control the lifecycle of the beans and share them. If we get these instances without a factory, we may end up creating a new instances for each of our use. Hence, the frameworks like Spring managing the beans using a factory are much lighter. The singleton beans are nothing but the shared invariant(client/context independent) data or processing instances.

Conclusion

We need a fine grained design for keeping our design simple and flexible. Since each new instance of a class has its own overhead, we may think it might need higher memory foot print.

But, the Flyweight pattern shows that with fine grained design we can carve out many invariant (client/context independent) components. By appropriately sharing these invariant instances, we can avoid it’s physical reoccurrence in the coarse grained versions. Thereby, we may end up building a lighter version of the application with our fine grained design.