Flyweight

Use sharing to support large numbers of fine-grained objects efficiently.

Introduction

  • Flyweight is a design pattern that belongs to the Structural Pattern group - design patterns that help ease design by defining a way to realize relationships between entities.

  • The Flyweight design pattern is a structural design pattern that allows you to fit more objects into the amount of available RAM by sharing and distributing public and private pieces of state among multiple objects instead of keeping all the data. in each object.

  • When many objects must be processed and the program cannot handle a huge amount of data, it is necessary to use a flyweight.

  • It is one of the Gang of Four designs.

  • Frequency of use: Low

Purpose is born

Problem

For example, you create an open world FPS game where players will move around the map and shoot each other. Added a lot of special effects, explosions, and lighting. Large amounts of bullets, missiles, and shrapnel from explosions will spread across the game world and bring an exciting experience to players.

After completing the game and send it to your friends to try it out. Although the game is running perfectly on your device, it cannot be played for long on other devices. The game keeps crashing after a few minutes of play. After a few hours of digging through the debug logs, you discover that the game crashes due to insufficient RAM. It turns out that the other device is much worse than your computer and that's why the problem appears so quickly on other machines.

For example, a bullet, a missile, or a piece of shrapnel is represented by a separate object that holds a lot of data. At some point, when the plethora of objects occurring on the player's screen peaks, the newly created objects no longer fill the remaining RAM, so the program crashes.

It's easy to guess, the reason is because each Particle class contains too much information: position, coordinates, vector, speed, color, sprite. Meanwhile, there are types of information that always change with each unit and types of information that are often the same between types of units. If one does not know about the flyweight pattern, one will have to give up and plan to raise the system requirements of the application - which is an unwise move.

Solution

Upon closer inspection of the Particle Class, you may notice that the color and sprite variables consume more memory than other fields. What's worse is that these two variables store almost identical data across all particles. For example, all bullets have the same color and sprite.

Like coordinates, motion vectors and speeds, are unique variables of each particle. The values ​​of these variables change over time. This data represents the ever-changing context in which that particle appears, while the color and sprite remain constant.

This constant data of an object is often called intrinsic state. It exists within the object; Other objects can only read it, not change it. The remainder of the object's state, which is often changed “externally” by other objects, is called the extrinsic state.

Extrinsic state storage

Where does the extrinsic state move to? Some class should still store it, right? In most cases, it gets moved to the container object, which aggregates objects before we apply the pattern.

In our case, that’s the main Game object that stores all particles in the particles field. To move the extrinsic state into this class, you need to create several array fields for storing coordinates, vectors, and speed of each individual particle. But that’s not all. You need another array for storing references to a specific flyweight that represents a particle. These arrays must be in sync so that you can access all data of a particle using the same index.

A more elegant solution is to create a separate context class that would store the extrinsic state along with reference to the flyweight object. This approach would require having just a single array in the container class.

Wait a second! Won’t we need to have as many 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 have been moved to just a few flyweight objects. Now, a thousand small contextual objects can reuse a single heavy flyweight object instead of storing a thousand copies of its data.

Flyweight and immutability

Since the same flyweight object can be used in different contexts, you have to make sure that its state can’t be modified. A flyweight should initialize its state just once, via constructor parameters. It shouldn’t expose any setters or public fields to other objects.

Flyweight factory

For more convenient access to various flyweights, you can create a factory method that manages a pool of existing flyweight objects. The method accepts the intrinsic state of the desired flyweight from a client, looks for an existing flyweight object matching this state, and returns it if it was found. If not, it creates a new flyweight and adds it to the pool.

There are several options where this method could be placed. The most obvious place is a flyweight container. Alternatively, you could create a new factory class. Or you could make the factory method static and put it inside an actual flyweight class.

Structure

Components in the model:

  • The Flyweight model is merely an optimization of the system. Before applying it, make sure that the program has RAM consumption problems related to having a large number of similar objects in memory at the same time.

  • The Flyweight class contains a portion of an object's initial state that can be shared among multiple objects. The same flyweight object can be used in many different contexts. The state stored inside a flyweight is called “intrinsic”. The state passed to flyweight methods is called “extrinsic”.

  • Class Context contains extrinsic state. When a context is paired with one of the flyweight objects, it represents the full state of the original object.

  • Normally, the original object's behavior still belongs to the Flyweight class. In this case, whenever calling a flyweight method, the appropriate values ​​of the extrinsic state must be passed into the method's parameters. Otherwise, the behavior can be passed to the Context class, which will use the associated flyweight merely as a data object

  • Client ( Classes that use Flyweight ) calculates or stores the extrinsic state of Flyweights. From a client perspective, Flyweight is a template object that can be configured at runtime by passing some contextual data into the parameters of its methods.

  • Flyweight Factory manages a fleet of existing flyweights. Clients will not generate flyweights directly. Instead, they call the factory, passing it the desired intrinsic state of the flyweight. factory looks at previously created flyweights and returns an existing one that matches the search criteria, or creates a new one if none is found.

In this example, the Flyweight Pattern helps reduce memory usage when needing to display millions of tree objects on the canvas.

At this point, each Tree object only has a location field and a type field, including TreeType objects. From there, trees with the same type will use the same TreeType object to help solve memory problems. In addition, the application also has a TreeFactory to easily manage TreeTypes.

Advantages & disadvantages

Advantage

  • Reduce the number of objects created by sharing objects. Thus saving memory and necessary storage devices

  • Improved data caching capabilities because of fast response times

  • Increase system performance

Disadvantage

  • Trade-off in terms of CPU usage when flyweight objects are accessed multiple times.

  • Code becomes much more complex. New team members will always wonder why an entity's state is separated in such a way. Understandability is low

When to use it

Flyweight is used when:

  • When there are a large number of objects created repeatedly by the application.

  • When object creation requires a lot of memory and time

  • When you want to reuse an existing object instead of having to spend time creating a new one

  • When a group of objects contains many similar objects and the two objects in the group are not much different

Applicability

Use the Flyweight pattern only when your program must support a huge number of objects which barely fit into available RAM.

The benefit of applying the pattern depends heavily on how and where it’s used. It’s most useful when:

  • an application needs to spawn a huge number of similar objects

  • this drains all available RAM on a target device

  • the objects contain duplicate states which can be extracted and shared between multiple objects

How to Implement

  1. Divide fields of a class that will become a flyweight into two parts:

    • the intrinsic state: the fields that contain unchanging data duplicated across many objects

    • the extrinsic state: the fields that contain contextual data unique to each object

  2. Leave the fields that represent the intrinsic state in the class, but make sure they’re immutable. They should take their initial values only inside the constructor.

  3. Go over methods that use fields of the extrinsic state. For each field used in the method, introduce a new parameter and use it instead of the field.

  4. Optionally, create a factory class to manage the pool of flyweights. It should check for an existing flyweight before creating a new one. Once the factory is in place, clients must only request flyweights through it. They should describe the desired flyweight by passing its intrinsic state to the factory.

  5. The client must store or calculate values of the extrinsic state (context) to be able to call methods of flyweight objects. For the sake of convenience, the extrinsic state along with the flyweight-referencing field may be moved to a separate context class.

Relations with Other Patterns

  • You can implement shared leaf nodes of the Composite tree as Flyweights to save some RAM.

  • Flyweight shows how to make lots of little objects, whereas Facade shows how to make a single object that represents an entire subsystem.

  • Flyweight would resemble Singleton if you somehow managed to reduce all shared states of the objects to just one flyweight object. But there are two fundamental differences between these patterns:

    1. There should be only one Singleton instance, whereas a Flyweight class can have multiple instances with different intrinsic states.

    2. The Singleton object can be mutable. Flyweight objects are immutable.

Last updated