Flyweight Pattern
Overview
A document may contain ten thousand glyphs but only a few font-and-size combinations. A separate style object for every glyph duplicates the same state.
Flyweight shares the state that can safely be reused. Shared internal data is called intrinsic state; data that varies by use is extrinsic state. Here style is shared while placement stays separate.
UML
StylePool reuses a GlyphStyle for each font-and-size pair. PlacedGlyph refers to that style but keeps its own letter and X coordinate.
History
GoF cataloged Flyweight as a structural pattern in Design Patterns in 1994. It uses shared state to support many small objects with less duplicated storage.1
Why use it?
Separating shared state from per-use state allows reuse. Mutating shared state would affect several users, so GlyphStyle is immutable.
A pool also grows if new keys accumulate. Define its lifetime and retention policy, and measure the workload before claiming a memory benefit.
Example
Request the same style twice and compare object identity.
using System;
using System.Collections.Generic;
using System.Linq;
var pool = new StylePool();
Console.WriteLine(ReferenceEquals(pool.Get("Serif", 16), pool.Get("Serif", 16)));
sealed record GlyphStyle(string Font, int Size);
sealed record PlacedGlyph(char Letter, int X, GlyphStyle Style);
sealed class StylePool
{
private readonly Dictionary<(string Font, int Size), GlyphStyle> styles = new();
public int Count => styles.Count;
public GlyphStyle Get(string font, int size)
{
var key = (font, size);
if (!styles.TryGetValue(key, out var style))
{
style = new GlyphStyle(font, size);
styles.Add(key, style);
}
return style;
}
}
TrueStylePool returns the object it already created instead of constructing two equal style objects.
An extended example
Place two different letters at different coordinates while sharing one style.
using System;
using System.Collections.Generic;
using System.Linq;
var pool = new StylePool();
var letters = new[]
{
new PlacedGlyph('A', 0, pool.Get("Serif", 16)),
new PlacedGlyph('B', 20, pool.Get("Serif", 16))
};
foreach (var letter in letters) Console.WriteLine(letter.Letter + "@" + letter.X);
Console.WriteLine("styles: " + pool.Count);
sealed record GlyphStyle(string Font, int Size);
sealed record PlacedGlyph(char Letter, int X, GlyphStyle Style);
sealed class StylePool
{
private readonly Dictionary<(string Font, int Size), GlyphStyle> styles = new();
public int Count => styles.Count;
public GlyphStyle Get(string font, int size)
{
var key = (font, size);
if (!styles.TryGetValue(key, out var style))
{
style = new GlyphStyle(font, size);
styles.Add(key, style);
}
return style;
}
}
A@0
B@20
styles: 1The letters and positions differ; their style is shared. Unlike Singleton, the pool can hold multiple kinds of shared objects. This pool is used on one thread.
Related reading
Each example is a separate .NET 10 console program.
Footnotes
- Gamma, Helm, Johnson, Vlissides. Design Patterns (1994). Publisher record. Structural patterns, Flyweight. β©