Flyweight Design Pattern
Classic GOF Structural Design Patterns
Intent
The Flyweight Pattern is used to reduce the number of objects created and to decrease memory footprint and increase performance. The flyweight pattern tries to reuse already existing similar kinds of objects by storing them and creates new objects when no matching object is found.
It is used to:
- share and support large numbers of fine-grained objects efficiently.
- reduce memory footprint for maintaining objects
Problem
The Flyweight Pattern is used to provide a balance to a common problem - granularity of objects versus memory and performance. Designing objects down to the lowest levels of granularity provides an amazing amount of flexibility; but, it comes at the cost (a sometimes unacceptable cost) of memory usage and performance.
Discussion
The Flyweight provides a pattern to use to allow sharing of objects and enabling them to be used at a fine-grained level without a prohibitive cost. How is this magic accomplished? Each Flyweight object is broken down into two pieces: an extrinsic and an intrinsic part. The intrinsic part is state-independent and is stored in the flyweight object to be reused and shared over and over again. The extrinsic part of the flyweight is state-dependent and is only passed to the flyweight object when operations on it are called by clients - when the call is done, the extrinsic data is returned to the client and no longer part of the flyweight.
A useful way to think about Flyweights in the everyday world is in the context of web browsers. Almost every modern browser takes advantage of flyweights to prevent fetching the same image multiple times. Imagine you have a web page where you have multiple copies of an image - all in different locations and with different sizes. The browser is smart enough (because of the flyweight pattern) to download the image once - this is the intrinsic part of the flyweight, it doesn't change no matter where or how it is used. The browser then takes that single shared image and places it all around the page based on position and size information provided - this is the extrinsic part of the flyweight and it is managed by the browser. The image knows nothing about how it is placed.
Structure
The structure of the Flyweight is fairly straightforward in almost every programming language. Fundamentally, it consists of a flyweight object and a factory which can serve them up as needed.
The most challenging part of the flyweight pattern is how to split up the data between extrinsic and intrinsic representations and where to store that external state data (extrinsic). In many cases, that external data is moved to a container that is specifically design to manage that on behalf of your application - this is a container. This context would be responsible for linking or associating the extrinsic state with a particular flyweight object. The context class would store all fields of extrinsic state - requiring just one array field for storing references to context objects.
But how does this benefit us? Isn't this still the same number of objects that we started with? While technically that is true, each one of the objects is much smaller than before - the most memory intensive fields are stored in the intrinsic elements of the flyweight which are shared. Thousands of these contextual objects can share a single flyweight object instead of each storing a full representation.
In this basic diagram of the Flyweight Pattern the Client communicates with a Context object which manages the extrinsic state and linking to flyweights. The Context can either retrieve a Flyweight object that it already has or can request a new one from the FlyweightFactory. In practically every scenario, the Flyweight Object is immutable - once it is created, it's internal or intrinsic data should never change. If this were not so, it would be very difficult to reuse them as intended.
It is important to remember that the Flyweight Pattern optimization only makes sense in problems that are dealing with a gigantic number of similar objects. The most important consideration to use it is to perform an analysis of your code to see if you would benefit from its use.
The structure is straightforward and consists of:
- 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.
- IFlyweight - declares an interface through which flyweights can receive and act on extrinsic state.
- ConcreteFlyweight - implements the IFlyweight interface and 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."
- 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.
Practical Example
For a practical example of the Flyweight Pattern in a real world usage scenario, lets focus on a simple scenario that is addressed in the original Gang of Four Book - a simple sharing of character objects within a document. Here we will be focusing on the fact that a document is composed of many characters and those characters can be used over and over again within the document.
First let's think about how to split up what should go into the flyweight object (intrinsic) and what should be stored as external (extrinsic) state. What are the immutable features of a character in a document? A quick reflection comes up with the following items:
- the actual symbol to display
- its width
- height
- font
Everything else about the character should most likely be extrinsic. This includes position in document, point Size, etc... Arguably, font could be extrinsic and height and width should be automatically calculated based on the point size; but, for our purposes, these divisions are adequate to demonstrate the flyweight pattern.
Much like the generic diagram in STRUCTURE above, the Flyweight will an efficient way of sharing flyweight objects (in this case characters) to be used repeatedly in a document. The only major difference between out implementation and the "idealized" structure is that we don't really need a full context for this example and so it will be handled by the client application directly.
Let's expand on this practical example with some code below.
Code Samples
Below, you will find examples of the Flyweight Pattern implemented in four different commonly used languages. Each one of the examples implements the exact same scenario as described in the PRACTICAL EXAMPLE section above.
Each sample will use a flyweight to allow for easily manage a huge number of characters in a document efficiently.
C# Flyweight Code Sample
In this C# example, the Flyweight pattern is used.
Java Flyweight Code Sample
In this Java example, the Flyweight pattern is used.
Typescript Flyweight Code Sample
In this TypeScript example, the Flyweight pattern is used.
JavaScript Flyweight Code Sample
In this JavaScript example, the Flyweight pattern is used.
Considerations
- Ensure that object overhead (memory footprint) is an issue needing attention, and, the client of the class is able and willing to absorb responsibility realignment.
- Divide fields of a class that should become a flyweight into two parts:
- intrinsic state for fields that contain unchanging data duplicated across many instances
- extrinsic state for fields that contain contextual data that is unique for each instance
- 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.
Additional Notes
- Saves RAM, thus allowing a program to support much more objects.
- Increases overall code complexity by creating multiple additional classes.
- An inefficient Context implementation can waste nearly as much in the way of CPU time in finding or calculating the actual context needed.
- Flyweight shows how to make lots of little objects and Facade shows how to make a single object represent an entire subsystem.
- Flyweight is often combined with Composite to implement shared leaf nodes.
- Flyweight explains when and how State objects can be shared.