Card 04/ 08

ExampleDifficulty: Intermediate2 min

Flyweight Example: Characters in a Text Editor

Each glyph's font and shape is shared; its position on the page is passed in.

The characters of a text editor are a natural Flyweight, and this example maps the template onto it. A document holds tens of thousands of characters, but only a few dozen distinct letters in a few fonts. Each distinct combination of character, font and size becomes one shared glyph, the drawable form of a character, and the page records where each one is drawn.

Each field of a character on the page, and its kind of state
FieldKindWhy
Character codeIntrinsicEvery t in the same font is drawn the same way
Font and sizeIntrinsicShared by every character in the same run of text
Row and columnExtrinsicDifferent for every character on the page
The template's participants in the editor example
TemplateEditor example
FlyweightGlyph, a frozen dataclass of character, font and size
FlyweightFactoryGlyphFactory, keyed by all three
ClientThe page, a list of glyph, row and column
py
from dataclasses import dataclass


@dataclass(frozen=True)
class Glyph:
    char: str
    font: str
    size: int

    def draw(self, row: int, column: int) -> str:
        return f"{self.char!r} in {self.font} {self.size} at {row}:{column}"


class GlyphFactory:
    def __init__(self) -> None:
        self._glyphs: dict[tuple[str, str, int], Glyph] = {}

    def get(self, char: str, font: str, size: int) -> Glyph:
        key = (char, font, size)
        if key not in self._glyphs:
            self._glyphs[key] = Glyph(char, font, size)
        return self._glyphs[key]

    def count(self) -> int:
        return len(self._glyphs)


factory = GlyphFactory()
text = "the cat sat on the mat " * 2000
page = [(factory.get(char, "Georgia", 12), 1 + i // 80, 1 + i % 80) for i, char in enumerate(text)]

for glyph, row, column in page[:3]:
    print(glyph.draw(row, column))
print(f"characters on the page: {len(page)}")
print(f"glyph objects: {factory.count()}")
print(f"the t in \"the\" and the t in \"cat\" are one object: {page[0][0] is page[6][0]}")
OutputRan with python:3.12-slim (Python 3.12.13)
't' in Georgia 12 at 1:1
'h' in Georgia 12 at 1:2
'e' in Georgia 12 at 1:3
characters on the page: 46000
glyph objects: 10
the t in "the" and the t in "cat" are one object: True
The page holds 46,000 entries and the factory holds ten glyphs.

The text is one short sentence repeated 2,000 times: 46,000 characters, drawn from ten distinct characters, including the space. The factory made ten glyph objects, and every t on the page refers to the same one. frozen=True makes each glyph immutable, so no code can change the font of one character and with it every other.

Changing the font of a word means asking the factory for glyphs in the new font, not editing the glyphs already shared. Real editors and browsers use the same idea for their glyph caches.