The Flyweight pattern is an efficient solution when you need to create large numbers of repeated objects. It is useful when your original design requires so many objects that represent the exact same things are created at run time that they take up too much memory. An example that Wal gave in class was for his parse tree, where so many node objects were being created when it was running that it filled up all the memory. Many of the node objects were exactly the same, for example an object to represent an "=" character, however a new object is created every time one is found. The Flyweight pattern provides a way to deal with these repeated objects so that only one of each type is ever stored in memory which is shared among whatever contexts need to use it.
Use Flyweight when all of the following are true:
- your application uses a lot of objects.
- storage costs are high because there are so many objects.
- most object state can be extracted from the object.
- groups of objects can be replaced by few shared objects.
- object identity is not important for the application, meaning that the application will work the same if objects are shared rather than if each instance is a separate object.
The Flyweight pattern structure looks like this:
- defines an abstract interface
- concrete objects can either be shared or unshared, it enables sharing but it doesn't enforce it.
- implements the Flyweight interface and adds storage for intrinsic state.
- must be sharable.
- any state it stores must be intrinsic, which means it must be independent of the ConcreteFlyweight object's context.
- implements the Flyweight interface and adds storage for a particular instance
- is not shared
- it's common for UnsharedConcreteFlyweight objects to have ConcreteFlyweight objects as children at some level in the flyweight object structure
- deals with the Flyweight objects in particular that they are shared properly. When a client requests a flyweight, the FlyweightFactory searches the pool of existing Flyweights and returns the Flyweight if it exists. If it hasn't been created yet, it creates a new Flyweight, adds it to the pool of existing Flyweights, and returns the Flyweight.
As listed above Flyweight objects can either be shared or unshared. It is important to note that the Flyweight pattern merely enables sharing, it does not enforce it. In the parse tree example, shared objects are for example an "int" type, where there can be many occurrences of the exact same object in various contexts. Unshared objects are for example an ID node that will have a different value field in different contexts, hence these objects cannot be shared.
The design of the Flyweight pattern may not seem like a great design, but is the best way to deal with the efficiency problem described earlier.
An example of where the Flyweight pattern would be appropriate would be in a graphics program where large amounts of textures are loaded and some of them are duplicates. Duplication of texture object would create a too greater cost on memory performance. The Flyweight pattern can avoid such unnecessary duplications. A Flyweight factor would handle the creation of textures. If a texture was requested of the Flyweight factor that already existed, then the Flyweight factor would return a shared object.
- Parse tree design: An example of the Flyweight pattern used in a parse tree design.
- Another example of real world can be found here with java code.
Flyweights introduce extra costs associated with managing the state of the shared objects. On the other hand, it can save a significant amount of space, especially when a large number of objects are shared. The space savings increase as more objects are shared and more object state is shared.
- Composite: The Flyweight pattern is often combined with Composite where the leaf nodes are shared.
- State and Strategy: Objects from these two patterns are often best implemented as Flyweights.
- Object pool
Creational: Abstract Factory | Builder | Factory Method | Prototype | Singleton