Files
freemo d9e5668cec fix(skills): comprehensive final audit pass for programming-patterns skill
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
2026-04-15 13:22:13 -04:00

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
  • 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.