We're working on a substantial update for the whole design patterns section that should be ready by the end of September. Until then, please sorry for all embarrassing typos and errors you might encounter here and there.
Flyweight is a structural design pattern that lets you fit more objects into the available amount of RAM by sharing common parts of object state among multiple objects, instead of keeping it in each object.
To have some fun after long working hours, you decided to create a simple video game. Players will be moving around the map and shoot each other. You decided to implement a realistic particle system and make it a distinctive feature of the game. Bullets, missiles, and shrapnel from explosions should fly over in vast quantities and deliver a thrilling experience to a player.
After a while, you pushed the last commit and sent the game to your friends, hoping to play it right away. Though the game was running flawlessly on your machine, your friend was not able to play. The game kept crashing soon after launching on his computer.
After few hours of digging in logs, you have discovered that the game crashes because of insufficient RAM. And it looks that problem is related to your particle system.
Each particle is represented by an object that contains plenty of data. At some point, when the carnage on screen reaches its climax, newly created particles no longer fit into remaining RAM, and the program crashes.
On a closer inspection of the
Particle class, you may notice that color and sprite data consumes the most memory in each object. And worse, these fields store almost identical data across all particles. For example, all bullets have the same color and texture.
The rest of particle data, such as coordinates, movement vector and speed are unique among all particles. Moreover, the values of these fields change over time. They look like a changeable context in which particle exists, while color and sprite remain constant.
Such variable context-specific object data is usually called "extrinsic state," because it is changed from the outside of the object. The rest of object state, the unchangeable data, is called "intrinsic state."
The Flyweight pattern suggests to stop storing the extrinsic state inside object fields, but rather pass it directly to methods that use it. This way you leave only unchangeable state inside an object, which allows you to reuse it in different contexts. And most importantly, you would need much less of these objects, since they only differ in intrinsic state, which does not vary that much.
For our game, only three particle objects would suffice (bullet, missile, and shrapnel). As you have probably guessed by now, such stripped objects are called flyweights.
Extrinsic state storage
But where to does the extrinsic state move? Some classes should keep it, right? In most cases, it is convenient to move this data to the container, which used to aggregate objects before we applied the pattern.
In our case, that would the main game object. You could have created extra array-type fields for storing coordinates, vectors, and speeds. Additionally, you would require another array for storing references to specific flyweight objects that represent a particle.
But the more elegant solution would be to create a separate context class that would link extrinsic state with a particular flyweight object. Since context class would store all fields of extrinsic state, the container class would require just one array field for storing references to context objects.
Wait for a second! Will not we need to have the same amount of these contextual objects as we had at the very beginning? Technically, yes. But the thing is, these objects are much smaller than before. The most memory consuming fields are now living in just several flyweight objects. Thousands of contextual objects can link and reuse a single flyweight object instead of storing its duplicate state inside.
Since the same flyweight objects will be used in different contexts, you have to make sure that their state can not be modified. Flyweights should receive their intrinsic state only through constructor's parameters. They should not expose setters or public fields.
You can create a factory method that would manage a pool of existing flyweights objects. The method would accept desired intrinsic state from a client, look for an existing flyweight object matching this state, and return it if it was found. If not, it will create new flyweight and add it to the pool.
There are several options where this method could be placed. The usual choice is a flyweight container. Alternatively, a new factory class could be created. You could even make the factory method static and put it inside the main Flyweight class.
Always remember that Flyweight pattern is the optimization that only makes sense in programs operating with a giant number of similar objects. The pattern divides the state of these objects into two parts: flyweight and context.
Flyweight holds the portion of original object's state that can be shared among multiple objects. The same flyweight object can be used in many different contexts. The state stored inside flyweight is called "intrinsic state." The other part of original object's state passed to flyweight's parameters is called "extrinsic state."
Context contains extrinsic state, which was unique across all original objects. When a context is paired with one of the flyweight objects, it represents the full state of the original object.
Most of the time, the behavior of original objects stays within the Flyweight class. In this case, whoever calls a flyweight method, must also pass extrinsic state into the method's parameters. On the other hand, the behavior can be moved to the Context class, which would use linked flyweight merely as a data object.
Client calculates or stores the extrinsic state of flyweights. From a client's perspective, a flyweight is a template object that can be configured at runtime by passing contextual data through parameters of its methods.
Flyweight Factory manages a pool of existing flyweights. Clients do not create Flyweights directly. Instead, they call a flyweight factory and describe the desired flyweight by passing intrinsic state to factory's creational method. Factory looks over previously created flyweights and either returns existing one or creates new if it had not be found.
In this example, the Flyweight pattern helps to render a million trees on a canvas. Pattern extracts repeating intrinsic state from a main
Tree class and puts it into a flyweight class
Now instead of storing the same data in multiple objects, it will be kept in several flyweight objects and linked to appropriate
Tree objects. The client code works with trees through a flyweight factory, which encapsulates the logic of reusing existing tree types in new tree objects.
When you have to support a huge amount of objects that hardly fit into given RAM.
The benefit from applying the Flyweight pattern relies heavily on how and where it is used. It is most useful when:
- an application needs to spawn a large number of objects;
- these objects consume all system RAM;
- objects contain duplicate state, that can be extracted and shared.
How to Implement
Divide fields of a class that should become a flyweight into two parts:
- intrinsic state: fields that contain unchanging data, duplicate across many objects;
- extrinsic state: fields that contain contextual data, unique for all objects.
Leave the fields that represent the intrinsic state in the class, but make sure they are immutable. They should accept any values only inside the constructor.
Turn the fields of the extrinsic state into the arguments of methods that referred to them.
Create a flyweight factory class. It should check for existing flyweight before creating a new one. Clients must request flyweights from the flyweight factory. They should describe the desired flyweight by passing its intrinsic state to the factory method.
Clients must store or calculate values of extrinsic state (context) to be able to call methods of flyweight objects.
Pros and Cons
- Saves RAM, thus allowing a program to support much more objects.
- Wastes CPU time on searching or calculating the context.
- Increases overall code complexity by creating multiple additional classes.
Relations with Other Patterns
- Singleton object can be mutable. Flyweight objects are immutable.
- There should be only one Singleton instance, whereas Flyweight class can have multiple instances with a different intrinsic state.