Flyweight Pattern¶
Intent¶
The Flyweight pattern uses sharing to support large numbers of fine-grained objects efficiently. A flyweight is a shared object that can be used in multiple contexts simultaneously, acting as an independent object in each context.
Problem¶
When your application needs to create a huge number of similar objects, memory consumption can become prohibitive:
- Creating millions of objects consumes too much memory
- Most of the object's state can be shared between objects
- Creating and destroying objects is expensive
- Objects differ only in a small portion of their state
For example, in a text editor, each character might have properties like font, size, color, and style. If you store these properties in every character object, a document with 1 million characters would consume enormous amounts of memory, even though most characters share the same formatting.
Solution¶
The Flyweight pattern suggests separating an object's state into two parts:
- Intrinsic state - The state that is shared across many objects and doesn't change
- Extrinsic state - The state that varies between objects and is context-dependent
The flyweight stores only the intrinsic state, making it shareable. Extrinsic state is stored or computed by client objects and passed to the flyweight when operations are invoked. This dramatically reduces memory consumption when many objects share the same intrinsic state.
Structure¶
Participants¶
- Flyweight
- Declares an interface for operations with extrinsic state
- Stores intrinsic state (shared)
- Must be shareable
- In our example:
Flyweightclass withsharedState_
- ConcreteFlyweight
- Implements the Flyweight interface
- Stores intrinsic state
- Must be shareable across contexts
- In our example:
Flyweightimplementation
- FlyweightFactory
- Creates and manages flyweight objects
- Ensures flyweights are shared properly
- Returns existing flyweights when requested
- In our example:
FlyweightFactoryclass
- Client
- Maintains references to flyweights
- Computes or stores extrinsic state
- Passes extrinsic state to flyweight operations
- In our example:
Contextstruct and client code
Implementation¶
Flyweight Class¶
FlyweightFactory Class¶
Context Usage¶
Usage Example¶
Applicability¶
Use the Flyweight pattern when:
- An application uses a large number of objects - Memory cost is high - Example: Text editor with millions of characters
- Storage costs are high because of the sheer quantity of objects - Most object state can be made extrinsic - Example: Game with thousands of particles
- Many groups of objects can be replaced by relatively few shared objects - Objects are similar and differ only in extrinsic state - Example: Forest with many trees of the same type
- The application doesn't depend on object identity - Flyweights are shared, so identity checks will fail - Can't use object identity (pointer comparison)
Consequences¶
Benefits¶
-
Reduced memory consumption - Share intrinsic state across many objects - Can save 90%+ memory in optimal cases - Critical for large-scale applications
-
Improved performance - Fewer objects to create and destroy - Better cache locality (shared objects) - Reduced garbage collection pressure
-
Scalability - Can handle much larger numbers of objects - Enables features that would be impractical otherwise - Example: Rendering millions of particles
Drawbacks¶
-
Increased complexity - Need to separate intrinsic and extrinsic state - Requires careful design - More code to maintain
-
Runtime costs - Computing/passing extrinsic state adds overhead - Factory lookup has cost - May offset memory savings in some cases
-
No object identity - Can't rely on pointer comparison - Shared objects can't be modified - Must be immutable (or carefully synchronized)
-
Thread safety concerns - Shared flyweights must be thread-safe - Factory access may need synchronization - More complex in multi-threaded environments
Intrinsic vs Extrinsic State¶
Intrinsic State (Shared)¶
Characteristics: - Stored in the flyweight - Independent of context - Shareable across objects - Immutable (shouldn't change)
Examples: - Character formatting: font, size, color, style - Tree properties: texture, model, color - Particle properties: sprite, animation
Extrinsic State (Unique)¶
Characteristics: - Stored in context or computed - Depends on flyweight's context - Varies per object - Can change
Examples: - Character properties: position in document - Tree properties: position, scale, rotation - Particle properties: position, velocity
Real-world Examples¶
-
Text Editors - Intrinsic: Font, size, color, style (shared across characters) - Extrinsic: Position, character value - Savings: 90%+ memory for large documents
-
Game Engines - Particle Systems - Intrinsic: Sprite, texture, animation - Extrinsic: Position, velocity, lifetime - Enables millions of particles simultaneously
-
Game Engines - Terrain - Intrinsic: Tile texture, type, properties - Extrinsic: Position in world - Massive terrain maps with minimal memory
-
Graphics - Icons/Sprites - Intrinsic: Image data, sprite sheet - Extrinsic: Screen position, scale, rotation - Reuse same image in multiple places
-
String Pooling - Intrinsic: String content - Extrinsic: References to string - Java/C# string interning uses this
-
UI Frameworks - Styles - Intrinsic: Font, colors, margins, padding - Extrinsic: Component position, text content - Share styles across many components
Implementation Considerations¶
1. Managing Extrinsic State¶
Client is responsible for:
2. Removing Flyweights¶
Consider when flyweights should be removed: - Never (simplest, works if set is bounded) - Reference counting (remove when no longer used) - Garbage collection (automatic cleanup)
3. Sharing Flyweights¶
Factory ensures sharing:
4. Immutability¶
Flyweights should be immutable: - Prevents one context from affecting others - Enables safe sharing - Thread-safe by default
5. Factory Variations¶
Different factory approaches:
Flyweight vs Similar Patterns¶
Flyweight vs Singleton¶
| Aspect | Flyweight | Singleton |
|---|---|---|
| Number | Many instances (one per unique intrinsic state) | Single instance |
| Purpose | Memory optimization through sharing | Single point of access |
| State | Immutable shared state | Can have mutable state |
| Factory | Factory manages pool | Not typically needed |
Flyweight vs Prototype¶
| Aspect | Flyweight | Prototype |
|---|---|---|
| Creation | Share existing objects | Clone/copy objects |
| Identity | Same object used in multiple contexts | Each context has separate copy |
| Memory | Saves memory through sharing | Each copy consumes memory |
| Mutability | Immutable | Can be mutable |
Flyweight vs State¶
| Aspect | Flyweight | State |
|---|---|---|
| Purpose | Share structure | Vary behavior |
| Mutability | Immutable | State can change |
| Quantity | Many flyweights | Typically few states |
Sample Output¶
Key Takeaways¶
- Flyweight optimizes memory by sharing common state across many objects
- Separates state into intrinsic (shared) and extrinsic (unique) parts
- Factory manages and ensures proper sharing of flyweights
- Flyweights must be immutable to be safely shared
- Can achieve 90%+ memory savings in optimal scenarios
- Critical for applications with millions of similar objects
- Commonly used in game engines, text editors, and graphics systems
- Trade-off: Saves memory but adds complexity and runtime overhead
- Cannot rely on object identity since objects are shared
- Factory pattern is typically used to manage flyweight instances