d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
11 KiB
11 KiB
Flyweight Pattern
Use sharing to support large numbers of fine-grained objects efficiently by separating intrinsic (shared) state from extrinsic (context-specific) state.
Problem
A text editor renders thousands of characters. Each character could carry font, size, and color data. Storing this per character wastes enormous memory when most characters share the same styling.
Solution
Extract the shared intrinsic state (font, size, color) into flyweight objects stored in a factory. Each character in the document only stores its extrinsic state (position, the character itself) and a reference to its flyweight style.
┌────────────────────┐ ┌──────────────────────┐
│ CharacterStyle │ │ StyleFactory │
│ (flyweight) │ │ + get_style(font, │
│ - font │◄───────│ size, color) │
│ - size │ │ - cache: map │
│ - color │ └──────────────────────┘
│ + render(char, x, y)│
└────────────────────┘
Extrinsic state (char, x, y) passed in at render time.
When to Use
- Your application creates a huge number of objects that share significant common state.
- Memory consumption is a concern and most object state can be made extrinsic.
- The shared objects are immutable (or effectively immutable once created).
When to Avoid
- You only have a small number of objects — the overhead of the factory isn't worth it.
- Objects don't share enough common state to benefit from sharing.
- Making state extrinsic complicates the code more than the memory savings justify.
Pseudocode
class CharacterStyle:
field font, size, color // intrinsic (shared)
constructor(font, size, color):
this.font = font
this.size = size
this.color = color
method render(char, x, y):
output char + " at (" + x + "," + y + ") [" + font + " " + size + "pt " + color + "]"
class StyleFactory:
field cache: map<string, CharacterStyle>
method get_style(font, size, color):
key = font + "-" + size + "-" + color
if key not in cache:
cache[key] = new CharacterStyle(font, size, color)
return cache[key]
method count(): return len(cache)
// Usage
factory = new StyleFactory()
document = [
(factory.get_style("Arial", 12, "black"), "H", 0, 0),
(factory.get_style("Arial", 12, "black"), "e", 1, 0),
(factory.get_style("Arial", 12, "red"), "l", 2, 0),
(factory.get_style("Arial", 12, "black"), "l", 3, 0),
(factory.get_style("Arial", 12, "black"), "o", 4, 0),
]
for (style, char, x, y) in document:
style.render(char, x, y)
print "Unique styles: " + factory.count()
Python
class CharacterStyle:
"""Flyweight: stores intrinsic state (font, size, color)."""
__slots__ = ("font", "size", "color")
def __init__(self, font: str, size: int, color: str) -> None:
self.font = font
self.size = size
self.color = color
def render(self, char: str, x: int, y: int) -> None:
print(f" '{char}' at ({x},{y}) [{self.font} {self.size}pt {self.color}]")
class StyleFactory:
"""Creates and caches flyweight CharacterStyle objects."""
def __init__(self) -> None:
self._cache: dict[str, CharacterStyle] = {}
def get_style(self, font: str, size: int, color: str) -> CharacterStyle:
key = f"{font}-{size}-{color}"
if key not in self._cache:
self._cache[key] = CharacterStyle(font, size, color)
print(f" [Factory] Created new style: {key}")
return self._cache[key]
@property
def count(self) -> int:
return len(self._cache)
if __name__ == "__main__":
factory = StyleFactory()
# Simulate a document with characters and positions
document_chars = [
("H", 0, 0, "Arial", 12, "black"),
("e", 1, 0, "Arial", 12, "black"),
("l", 2, 0, "Arial", 12, "red"),
("l", 3, 0, "Arial", 12, "black"),
("o", 4, 0, "Arial", 12, "black"),
(" ", 5, 0, "Arial", 12, "black"),
("W", 6, 0, "Arial", 16, "blue"),
("o", 7, 0, "Arial", 16, "blue"),
("r", 8, 0, "Arial", 16, "blue"),
("l", 9, 0, "Arial", 12, "red"),
("d", 10, 0, "Arial", 12, "black"),
]
print("--- Building styles ---")
rendered: list[tuple[CharacterStyle, str, int, int]] = []
for char, x, y, font, size, color in document_chars:
style = factory.get_style(font, size, color)
rendered.append((style, char, x, y))
print(f"\n--- Rendering ({len(rendered)} chars, {factory.count} unique styles) ---")
for style, char, x, y in rendered:
style.render(char, x, y)
print(f"\nTotal characters: {len(rendered)}")
print(f"Unique style objects: {factory.count}")
print(f"Memory saved: {len(rendered) - factory.count} duplicate style objects avoided")
Output:
--- Building styles ---
[Factory] Created new style: Arial-12-black
[Factory] Created new style: Arial-12-red
[Factory] Created new style: Arial-16-blue
--- Rendering (11 chars, 3 unique styles) ---
'H' at (0,0) [Arial 12pt black]
'e' at (1,0) [Arial 12pt black]
'l' at (2,0) [Arial 12pt red]
'l' at (3,0) [Arial 12pt black]
'o' at (4,0) [Arial 12pt black]
' ' at (5,0) [Arial 12pt black]
'W' at (6,0) [Arial 16pt blue]
'o' at (7,0) [Arial 16pt blue]
'r' at (8,0) [Arial 16pt blue]
'l' at (9,0) [Arial 12pt red]
'd' at (10,0) [Arial 12pt black]
Total characters: 11
Unique style objects: 3
Memory saved: 8 duplicate style objects avoided
Go
package main
import "fmt"
// CharacterStyle is the flyweight (intrinsic state).
type CharacterStyle struct {
Font string
Size int
Color string
}
func (cs *CharacterStyle) Render(char string, x, y int) {
fmt.Printf(" '%s' at (%d,%d) [%s %dpt %s]\n", char, x, y, cs.Font, cs.Size, cs.Color)
}
// StyleFactory manages flyweight creation and caching.
type StyleFactory struct {
cache map[string]*CharacterStyle
}
func NewStyleFactory() *StyleFactory {
return &StyleFactory{cache: make(map[string]*CharacterStyle)}
}
func (f *StyleFactory) GetStyle(font string, size int, color string) *CharacterStyle {
key := fmt.Sprintf("%s-%d-%s", font, size, color)
if style, ok := f.cache[key]; ok {
return style
}
fmt.Printf(" [Factory] Created new style: %s\n", key)
style := &CharacterStyle{Font: font, Size: size, Color: color}
f.cache[key] = style
return style
}
func (f *StyleFactory) Count() int {
return len(f.cache)
}
type docChar struct {
char string
x, y int
font string
size int
color string
}
func main() {
factory := NewStyleFactory()
document := []docChar{
{"H", 0, 0, "Arial", 12, "black"},
{"e", 1, 0, "Arial", 12, "black"},
{"l", 2, 0, "Arial", 12, "red"},
{"l", 3, 0, "Arial", 12, "black"},
{"o", 4, 0, "Arial", 12, "black"},
{" ", 5, 0, "Arial", 12, "black"},
{"W", 6, 0, "Arial", 16, "blue"},
{"o", 7, 0, "Arial", 16, "blue"},
{"r", 8, 0, "Arial", 16, "blue"},
{"l", 9, 0, "Arial", 12, "red"},
{"d", 10, 0, "Arial", 12, "black"},
}
type rendered struct {
style *CharacterStyle
char string
x, y int
}
fmt.Println("--- Building styles ---")
var chars []rendered
for _, dc := range document {
style := factory.GetStyle(dc.font, dc.size, dc.color)
chars = append(chars, rendered{style, dc.char, dc.x, dc.y})
}
fmt.Printf("\n--- Rendering (%d chars, %d unique styles) ---\n", len(chars), factory.Count())
for _, c := range chars {
c.style.Render(c.char, c.x, c.y)
}
fmt.Printf("\nTotal characters: %d\n", len(chars))
fmt.Printf("Unique style objects: %d\n", factory.Count())
fmt.Printf("Memory saved: %d duplicate style objects avoided\n", len(chars)-factory.Count())
}
Output:
--- Building styles ---
[Factory] Created new style: Arial-12-black
[Factory] Created new style: Arial-12-red
[Factory] Created new style: Arial-16-blue
--- Rendering (11 chars, 3 unique styles) ---
'H' at (0,0) [Arial 12pt black]
'e' at (1,0) [Arial 12pt black]
'l' at (2,0) [Arial 12pt red]
'l' at (3,0) [Arial 12pt black]
'o' at (4,0) [Arial 12pt black]
' ' at (5,0) [Arial 12pt black]
'W' at (6,0) [Arial 16pt blue]
'o' at (7,0) [Arial 16pt blue]
'r' at (8,0) [Arial 16pt blue]
'l' at (9,0) [Arial 12pt red]
'd' at (10,0) [Arial 12pt black]
Total characters: 11
Unique style objects: 3
Memory saved: 8 duplicate style objects avoided
JavaScript
class CharacterStyle {
constructor(font, size, color) {
this.font = font;
this.size = size;
this.color = color;
}
render(char, x, y) {
console.log(
` '${char}' at (${x},${y}) [${this.font} ${this.size}pt ${this.color}]`
);
}
}
class StyleFactory {
constructor() {
this._cache = new Map();
}
getStyle(font, size, color) {
const key = `${font}-${size}-${color}`;
if (!this._cache.has(key)) {
console.log(` [Factory] Created new style: ${key}`);
this._cache.set(key, new CharacterStyle(font, size, color));
}
return this._cache.get(key);
}
get count() {
return this._cache.size;
}
}
// --- Main ---
const factory = new StyleFactory();
const document = [
["H", 0, 0, "Arial", 12, "black"],
["e", 1, 0, "Arial", 12, "black"],
["l", 2, 0, "Arial", 12, "red"],
["l", 3, 0, "Arial", 12, "black"],
["o", 4, 0, "Arial", 12, "black"],
[" ", 5, 0, "Arial", 12, "black"],
["W", 6, 0, "Arial", 16, "blue"],
["o", 7, 0, "Arial", 16, "blue"],
["r", 8, 0, "Arial", 16, "blue"],
["l", 9, 0, "Arial", 12, "red"],
["d", 10, 0, "Arial", 12, "black"],
];
console.log("--- Building styles ---");
const rendered = document.map(([char, x, y, font, size, color]) => ({
style: factory.getStyle(font, size, color),
char,
x,
y,
}));
console.log(
`\n--- Rendering (${rendered.length} chars, ${factory.count} unique styles) ---`
);
for (const { style, char, x, y } of rendered) {
style.render(char, x, y);
}
console.log(`\nTotal characters: ${rendered.length}`);
console.log(`Unique style objects: ${factory.count}`);
console.log(
`Memory saved: ${rendered.length - factory.count} duplicate style objects avoided`
);
Output:
--- Building styles ---
[Factory] Created new style: Arial-12-black
[Factory] Created new style: Arial-12-red
[Factory] Created new style: Arial-16-blue
--- Rendering (11 chars, 3 unique styles) ---
'H' at (0,0) [Arial 12pt black]
'e' at (1,0) [Arial 12pt black]
'l' at (2,0) [Arial 12pt red]
'l' at (3,0) [Arial 12pt black]
'o' at (4,0) [Arial 12pt black]
' ' at (5,0) [Arial 12pt black]
'W' at (6,0) [Arial 16pt blue]
'o' at (7,0) [Arial 16pt blue]
'r' at (8,0) [Arial 16pt blue]
'l' at (9,0) [Arial 12pt red]
'd' at (10,0) [Arial 12pt black]
Total characters: 11
Unique style objects: 3
Memory saved: 8 duplicate style objects avoided
Related Patterns
- Composite — flyweight is often applied to leaf nodes in composite trees to reduce memory when many identical leaves exist.
- Object Pool — object pool recycles expensive mutable objects; flyweight shares immutable intrinsic state.
- Prototype — prototype creates new objects by cloning; flyweight avoids creating new objects by sharing existing ones.
- Factory — a flyweight factory manages the cache of shared flyweight instances.