Home Design Patterns Flyweight
Flyweight
Structural Gang of Four“Use sharing to support large numbers of fine-grained objects efficiently.”
Gamma, Helm, Johnson & Vlissides, p. 195Purpose
Flyweight allows multiple objects to share data. This is typically done to make better use of available memory. It can also improve processor efficiency. An example of such speed increases would be the case of 3D textures pre-loaded into a graphics card. Without preloading this shared data, the GPU bus would be overloaded.
Design
Separate the object's state information into intrinsic and extrinsic data. Think about what state information is the same across a group of objects. Put the unchanging, shared information into the flyweight and reference that information from each of the individual objects that use it.
This only works if you have a large amount of state information that is shared among objects. If the intrinsic portion is small, or if almost every object has its own variant of it, the bookkeeping costs more than the sharing saves.
A factory is usually required, not optional. If callers can construct flyweights directly, nothing guarantees that two requests for the same intrinsic state return the same instance, and the entire benefit evaporates.
Example
Autobot Robotics has begun experimenting in artificial intelligence. It quickly became apparent that even the simplest algorithms would bring a modern computer to its knees from memory requirements alone.
In order to reduce this problem, the designers chose to share as much state information as was possible between objects. The system keeps a history and creates an object for each event the machine encounters. Rather than storing the entire environment for each object that was discovered, they chose to store that state one time and have all of the objects that use that environment share that data.
class FlyweightFactory
{
EnvState[] envTable = new EnvState[1000];
public int getFlyweight(EnvState env)
{
int hashNum = GetHashCode(env);
if (envTable[hashNum] != null)
return hashNum;
else
{
return createEnv();
}
}
private int GetHashCode(EnvState env)
{
return 0; // return the hashed env
}
private int createEnv() {
int newHashNum = 0;
// create an environment and load it into the hash table
return newHashNum;
}
}
class FlyWeight
{
public bool checkAction(int key);
}
class ConcreteEnvironment : FlyWeight
{
public bool checkAction(int key)
{
bool shouldDo = false;
// check to see if we should do this
return shouldDo;
}
}
class EnvironmentItem : FlyWeight
{
public bool checkAction(int key)
{
bool shouldDo = false;
// check to see if we should do this
return shouldDo;
}
}
envTable is the pool, and
getFlyweight() is the gate that keeps it authoritative: callers
receive a handle rather than constructing environments of their own.Comparisons
- Object Pool is the pattern people most often mean when they say Flyweight. A pool hands out exclusive objects and takes them back; a flyweight is used by many clients at once and is never returned. Pools save construction cost, flyweights save memory.
- Composite pairs well with it, because shared leaf nodes let one object appear at many positions in a tree.
- Multiton has nearly the same implementation, a keyed map of instances, but its purpose is guaranteeing one instance per key rather than reducing footprint.
- Singleton is the degenerate case: a flyweight pool with exactly one key.
Why It Exists
My take
Flyweight exists because object-oriented modeling and memory efficiency pull in opposite directions. The natural OO instinct is one object per real-world thing, fully self-contained. That instinct is correct for hundreds of objects and catastrophic for tens of millions, and the pattern is the concession you make when the count crosses that line.
What is unusual about it is that it is the only structural pattern in the book whose justification is a hardware constraint rather than a design one. Every other pattern here is arguing about coupling and change. Flyweight is arguing about bytes. That gives it a very different character: you can measure whether it worked, and you should, because the answer is often that it did not.
The trap I would warn about is that the pattern is most tempting exactly when it is least appropriate. Splitting intrinsic from extrinsic state is genuinely interesting work, so it feels productive. But if you have not measured the footprint first, you are very likely adding indirection, a factory, a lifetime problem, and a thread-safety problem in exchange for saving memory you were never short of.
Criticisms
The flyweight breaks encapsulation by splitting the object into separate components. This can make the code more brittle. It can also make concurrency a greater challenge, because a shared object read by many threads must be genuinely immutable, and the pattern gives you no help enforcing that.
The concept of equality becomes confusing in a flyweight, since each of the objects is using exactly the same data. Two logically distinct things are now reference-identical, so any code that used identity to tell them apart is silently wrong.
There is also a lifetime question the pattern does not answer. The pool holds references, so flyweights are never garbage collected unless you actively evict them, and deciding when it is safe to evict means knowing something about clients the pool was designed not to know about.