1. Overview
In this article, we’ll take a look at the flyweight design pattern. This pattern is used to reduce the memory footprint. It can also improve performance in applications where object instantiation is expensive.
Simply put, the flyweight pattern is based on a factory which recycles created objects by storing them after creation. Each time an object is requested, the factory looks up the object in order to check if it’s already been created. If it has, the existing object is returned – otherwise, a new one is created, stored and then returned.
The flyweight object’s state is made up of an invariant component shared with other similar objects (intrinsic) and a variant component which can be manipulated by the client code (extrinsic).
It’s very important that the flyweight objects are immutable: any operation on the state must be performed by the factory.
2. Implementation
The main elements of the pattern are:
- an interface which defines the operations that the client code can perform on the flyweight object
- one or more concrete implementations of our interface
- a factory to handle objects instantiation and caching
Let’s see how to implement each component.
2.1. Vehicle Interface
To begin with, we’ll create a Vehicle interface. Since this interface will be the return type of the factory method we need to make sure to expose all the relevant methods:
public void start(); public void stop(); public Color getColor();
2.2. Concrete Vehicle
Next up, let’s make a Car class as a concrete Vehicle. Our car will implement all the methods of the vehicle interface. As for its state, it’ll have an engine and a color field:
private Engine engine; private Color color;
2.3. Vehicle Factory
Last but not least, we’ll create the VehicleFactory. Building a new vehicle is a very expensive operation so the factory will only create one vehicle per color.
In order to do that, we keep track of the created vehicles using a map as a simple cache:
private static Map<Color, Vehicle> vehiclesCache = new HashMap<>(); public static Vehicle createVehicle(Color color) { Vehicle newVehicle = vehiclesCache.computeIfAbsent(color, newColor -> { Engine newEngine = new Engine(); return new Car(newEngine, newColor); }); return newVehicle; }
Notice how the client code can only affect the extrinsic state of the object (the color of our vehicle) passing it as an argument to the createVehicle method.
3. Use Cases
3.1. Data Compression
The goal of the flyweight pattern is to reduce memory usage by sharing as much data as possible, hence, it’s a good basis for lossless compression algorithms. In this case, each flyweight object acts as a pointer with its extrinsic state being the context-dependent information.
A classic example of this usage is in a word processor. Here, each character is a flyweight object which shares the data needed for the rendering. As a result, only the position of the character inside the document takes up additional memory.
3.2. Data Caching
Many modern applications use caches to improve response time. The flyweight pattern is similar to the core concept of a cache and can fit this purpose well.
Of course, there are a few key differences in complexity and implementation between this pattern and a typical, general-purpose cache.
4. Conclusion
To sum up, this quick tutorial focused on the flyweight design pattern in Java. We also checked out some of the most common scenarios that involve the pattern.
All the code from the examples is available over on the GitHub project.