Flyweight
Intent
Use sharing to support large numbers of fine-grained objects efficiently.
Problem
A text editor must render thousands of characters on screen. Each character has font and style data that is often identical across many characters. Storing a separate copy of this shared data for every character wastes memory. You need a way to share common state among many objects.
Real-World Analogy
Think of a font in a word processor. The letter “e” might appear thousands of times in a document, but the software doesn’t store thousands of separate “e” objects. It stores one shared definition of “e” (the glyph) and just records where each one goes on the page. The glyph is the flyweight — shared data — and the position is the unique part stored separately.
When You Need It
- You’re rendering a forest with 100,000 trees in a game — each tree shares the same mesh and texture (flyweight), but has its own position, size, and rotation
- You’re building a text editor that displays millions of characters — the font style objects (Arial 12pt bold) are shared across all characters that use them
- You’re creating a map application with thousands of pins that share the same icon image but have different coordinates and labels
UML Class Diagram
classDiagram
class CharacterStyle {
-font: string
-size: int
-bold: bool
+Render(char: char, row: int, col: int)
}
class CharacterStyleFactory {
-cache: Map~string, CharacterStyle~
+GetStyle(font: string, size: int, bold: bool) CharacterStyle
}
CharacterStyleFactory --> CharacterStyle
Sequence Diagram
sequenceDiagram
Client->>Factory: getFlyweight(key)
alt not cached
Factory->>Flyweight: create
end
Factory-->>Client: flyweight
Client->>Flyweight: operation(extrinsicState)
Participants
| Participant | Role |
|---|---|
CharacterStyleFactory |
FlyweightFactory – creates and manages flyweight objects; ensures sharing. |
CharacterStyle |
Flyweight / ConcreteFlyweight – stores intrinsic (shared) state such as font and size. |
How It Works
- The factory maintains a cache keyed by the combination of font, size, and bold flag.
- When a character needs rendering, the client requests a
CharacterStylefrom the factory. - If a matching style exists in the cache, it is returned. Otherwise a new one is created and cached.
- Extrinsic state (the character value and its position) is passed to the
Rendermethod rather than stored in the flyweight.
Applicability
- An application uses a large number of objects that have significant shared state.
- Object identity is not important to the application.
- Most object state can be made extrinsic.
Trade-offs
Pros:
- Dramatically reduces memory usage when many similar objects exist
- Shared state is managed centrally, reducing duplication
- Works well for immutable shared data (fonts, icons, textures)
Cons:
- Adds complexity by splitting object state into intrinsic (shared) and extrinsic (unique) parts
- May trade memory savings for CPU overhead when computing extrinsic state
- Makes code harder to understand — objects no longer contain all their own data
Example Code
C#
public class CharacterStyle
{
public string Font { get; }
public int Size { get; }
public bool Bold { get; }
public CharacterStyle(string font, int size, bool bold)
{
Font = font; Size = size; Bold = bold;
}
public void Render(char c, int row, int col)
{
Console.WriteLine($"'{c}' at ({row},{col}) [{Font} {Size}pt{(Bold ? " bold" : "")}]");
}
}
public class CharacterStyleFactory
{
private readonly Dictionary<string, CharacterStyle> _cache = new();
public CharacterStyle GetStyle(string font, int size, bool bold)
{
string key = $"{font}_{size}_{bold}";
if (!_cache.ContainsKey(key))
_cache[key] = new CharacterStyle(font, size, bold);
return _cache[key];
}
}
Delphi
type
TCharacterStyle = class
private
FFont: string;
FSize: Integer;
FBold: Boolean;
public
constructor Create(const AFont: string; ASize: Integer; ABold: Boolean);
procedure Render(C: Char; Row, Col: Integer);
end;
TCharacterStyleFactory = class
private
FCache: TDictionary<string, TCharacterStyle>;
public
constructor Create;
destructor Destroy; override;
function GetStyle(const AFont: string; ASize: Integer; ABold: Boolean): TCharacterStyle;
end;
constructor TCharacterStyle.Create(const AFont: string; ASize: Integer; ABold: Boolean);
begin
FFont := AFont; FSize := ASize; FBold := ABold;
end;
procedure TCharacterStyle.Render(C: Char; Row, Col: Integer);
begin
WriteLn(Format('''%s'' at (%d,%d) [%s %dpt]', [C, Row, Col, FFont, FSize]));
end;
constructor TCharacterStyleFactory.Create;
begin
FCache := TDictionary<string, TCharacterStyle>.Create;
end;
destructor TCharacterStyleFactory.Destroy;
var
Style: TCharacterStyle;
begin
for Style in FCache.Values do
Style.Free;
FCache.Free;
inherited;
end;
function TCharacterStyleFactory.GetStyle(const AFont: string; ASize: Integer; ABold: Boolean): TCharacterStyle;
var
Key: string;
begin
Key := Format('%s_%d_%s', [AFont, ASize, BoolToStr(ABold, True)]);
if not FCache.TryGetValue(Key, Result) then
begin
Result := TCharacterStyle.Create(AFont, ASize, ABold);
FCache.Add(Key, Result);
end;
end;
C++
#include <string>
#include <unordered_map>
#include <memory>
#include <iostream>
class CharacterStyle {
std::string font_;
int size_;
bool bold_;
public:
CharacterStyle(std::string font, int size, bool bold)
: font_(std::move(font)), size_(size), bold_(bold) {}
void Render(char c, int row, int col) {
std::cout << "'" << c << "' at (" << row << "," << col
<< ") [" << font_ << " " << size_ << "pt"
<< (bold_ ? " bold" : "") << "]\n";
}
};
class CharacterStyleFactory {
std::unordered_map<std::string, std::shared_ptr<CharacterStyle>> cache_;
public:
std::shared_ptr<CharacterStyle> GetStyle(const std::string& font, int size, bool bold) {
std::string key = font + "_" + std::to_string(size) + "_" + std::to_string(bold);
auto it = cache_.find(key);
if (it == cache_.end()) {
auto style = std::make_shared<CharacterStyle>(font, size, bold);
cache_[key] = style;
return style;
}
return it->second;
}
};
Runnable Examples
| Language | Source |
|---|---|
| C# | Flyweight.cs |
| C++ | flyweight.cpp |
| Delphi | flyweight.pas |