Flyweight Pattern
Flyweight Pattern
Use sharing to support a large number of fine-grained objects efficiently by separating intrinsic (shared) state from extrinsic (context-specific) state.
Intent
The Flyweight pattern divides object state into two categories. Intrinsic state is stored inside the flyweight object: it is shared across all contexts where the flyweight is used, and it is immutable — it never changes after construction. Extrinsic state is not stored in the flyweight: it varies per context and is passed by the caller at call time. A FlyweightFactory maintains a cache (typically a Map keyed on intrinsic state) and returns the existing shared instance rather than creating a new one. The memory reduction comes from having one flyweight object serve thousands of different calling contexts — each context passes its own extrinsic state, so the flyweight remains reusable.
Real-world examples: Java String pool (interning identical strings to one object), browser text rendering glyph caches (one GlyphFlyweight per character+font pair serves every occurrence on the page), game engine particle systems (one Particle flyweight per asset type serves millions of on-screen particles with per-particle position/velocity as extrinsic state).
When NOT to Use
- Object count is modest (hundreds) — the factory cache and intrinsic/extrinsic split add complexity that is premature optimisation at this scale
- No significant shared state exists — if there is nothing to extract as intrinsic state, the pattern does not apply
- Extrinsic state is difficult to compute or pass at call time — the coordination cost outweighs the memory savings
- The objects are not performance-sensitive — simpler designs are preferred when memory pressure is not a concern
When to Use
- The application creates a very large number of objects (thousands to millions) and memory pressure is measurable
- Objects share significant common state that can be extracted as intrinsic (immutable, shared)
- Extrinsic state can be computed or passed by callers at call time, rather than stored per-object
- Memory reduction justifies the added complexity (benchmark before applying — measure the savings first)
How It Works
Participants:
- Flyweight — the interface declaring the operation that accepts extrinsic state as parameters.
- ConcreteFlyweight — stores intrinsic state (immutable, set at construction); implements the Flyweight operation, using the passed extrinsic parameters for context-specific behaviour.
- FlyweightFactory — maintains a cache
Map<key, Flyweight>; on aget(key)call, returns the existing shared instance if present, or creates and caches a new one. Often implemented as a Singleton. - Client — holds extrinsic state per context; retrieves flyweights from the factory; passes extrinsic state as arguments to each operation call.
The key design decision at implementation time is identifying what is intrinsic versus extrinsic. A useful heuristic: intrinsic state is whatever would be identical across all occurrences of a flyweight in different contexts.
Class Diagram
classDiagram
class Flyweight {
<<interface>>
+operation(extrinsicState)*
}
class ConcreteFlyweight {
-intrinsicState
+operation(extrinsicState)
}
class FlyweightFactory {
-cache : Map~key, Flyweight~
+getFlyweight(key) Flyweight
}
class Context {
-extrinsicState
-flyweight : Flyweight
+render()
}
Flyweight <|.. ConcreteFlyweight
FlyweightFactory o-- Flyweight : manages pool
Context --> Flyweight : uses
FlyweightFactory ..> ConcreteFlyweight : creates if absent
note for ConcreteFlyweight "Intrinsic state: shared, immutable\n(e.g., glyph shape, icon texture)"
note for Context "Extrinsic state: unique per use,\npassed at runtime (e.g., position, colour)"
TypeScript Example
// Flyweight — TypeScript
// Source: GoF Design Patterns, p.195
// Intrinsic state: shared, immutable (character, font)
// Extrinsic state: passed by caller (position, color)
class GlyphFlyweight {
constructor(
private readonly char: string, // intrinsic
private readonly font: string // intrinsic
) {}
render(x: number, y: number, color: string) { // extrinsic
console.log(`Render '${this.char}' at (${x},${y}) ${color} [${this.font}]`);
}
}
class GlyphFactory {
private cache = new Map<string, GlyphFlyweight>();
get(char: string, font: string): GlyphFlyweight {
const key = `${char}:${font}`;
if (!this.cache.has(key)) this.cache.set(key, new GlyphFlyweight(char, font));
return this.cache.get(key)!;
}
}
// Usage — one GlyphFlyweight per char+font, reused across thousands of render calls
const factory = new GlyphFactory();
const g = factory.get("A", "Arial");
g.render(10, 20, "black"); // extrinsic: position and color vary per call
g.render(50, 80, "red"); // same flyweight instance, different extrinsic stateJava Example
// Flyweight — Java
// Source: GoF Design Patterns, p.195
class GlyphFlyweight {
private final String ch; // intrinsic
private final String font; // intrinsic
GlyphFlyweight(String ch, String font) { this.ch = ch; this.font = font; }
void render(int x, int y, String color) { // extrinsic
System.out.printf("Render '%s' at (%d,%d) %s [%s]%n", ch, x, y, color, font);
}
}
class GlyphFactory {
private final Map<String, GlyphFlyweight> cache = new HashMap<>();
GlyphFlyweight get(String ch, String font) {
String key = ch + ":" + font;
return cache.computeIfAbsent(key, k -> new GlyphFlyweight(ch, font));
}
}Related Concepts
| Pattern | Relationship |
|---|---|
| Composite-Pattern | Flyweight is often used inside Composite leaf nodes to share state across many similar leaves |
| Proxy-Pattern | Both involve a level of indirection; Proxy controls access to an object, Flyweight shares state across contexts |
| Singleton-Pattern | FlyweightFactory is often implemented as a Singleton to ensure one shared cache exists per application |
Sources
- Gamma, Helm, Johnson, Vlissides — Design Patterns, Addison-Wesley, 1994, p.195
Backlinks
Notes that link here: GoF-Patterns-MOC