0

Flyweight — sharing common state to save memory

Advanced5 min read·eng-03-007
performancecompare

Concept

Flyweight pattern uses sharing to efficiently support large numbers of fine-grained objects. When many objects share the same state, that shared state is extracted and stored once in a Flyweight object.

Intrinsic vs Extrinsic state:

  • Intrinsic state: Shared, context-independent state stored in the Flyweight. Example: a character's font, size, and style in a text editor.
  • Extrinsic state: Context-specific state passed as parameters. Example: the character's position on the page.

The problem: A text editor has 1 million characters. Each character is an object. Without Flyweight: 1M objects, each storing: character code, font, size, style, position. With Flyweight: ~100 unique Flyweight objects (one per unique font/size/style combo), each character just references the right Flyweight + stores its own position.

Flyweight Factory: A factory that maintains a pool of existing Flyweights. When you request a Flyweight with given intrinsic state, the factory returns an existing one if available, or creates a new one.

When to use: When you have a very large number of objects, most of their state can be made extrinsic, and the application doesn't depend on object identity.

PHP context: Less commonly needed in web apps (request-scoped, short-lived). Relevant in: parsing large documents, game engines (PHP-CLI games), long-running Swoole applications with large in-memory data.

Code Example

php
<?php
// Flyweight — shared intrinsic state
class FontStyle
{
    public function __construct(
        public readonly string $fontFamily,
        public readonly int    $fontSize,
        public readonly string $fontWeight, // 'normal', 'bold'
        public readonly string $color,
    ) {}
}

// Flyweight Factory — ensures sharing
class FontStyleFactory
{
    private array $styles = []; // key → FontStyle

    public function get(string $family, int $size, string $weight, string $color): FontStyle
    {
        $key = "{$family}|{$size}|{$weight}|{$color}";
        if (!isset($this->styles[$key])) {
            $this->styles[$key] = new FontStyle($family, $size, $weight, $color);
        }
        return $this->styles[$key]; // return shared instance
    }

    public function getCount(): int { return count($this->styles); }
}

// Context — individual character with extrinsic state + shared Flyweight
class Character
{
    public function __construct(
        public readonly string    $char,    // 'H', 'e', 'l', ...
        public readonly int       $x,       // position (extrinsic)
        public readonly int       $y,
        public readonly FontStyle $style,   // SHARED intrinsic state (reference)
    ) {}

    public function render(): string
    {
        return "Render '{$this->char}' at ({$this->x},{$this->y}) "
             . "in {$this->style->fontFamily} {$this->style->fontSize}px "
             . "{$this->style->fontWeight} {$this->style->color}";
    }
}

// Usage
$factory = new FontStyleFactory();

$characters = [];
$text = "Hello, World! This is a long document with many characters.";
$x = 0;

foreach (str_split($text) as $i => $char) {
    // Most characters share the same style
    $style = $factory->get('Arial', 12, 'normal', '#000000');
    // Some might be bold or different color
    if ($char === 'H') {
        $style = $factory->get('Arial', 12, 'bold', '#FF0000');
    }
    $characters[] = new Character($char, $x, 10, $style);
    $x += 8;
}

echo "Characters: " . count($characters) . "\n"; // e.g., 58
echo "Unique styles: " . $factory->getCount() . "\n"; // 2 (normal + bold red 'H')
// Without Flyweight: 58 FontStyle objects
// With Flyweight: 2 FontStyle objects, shared across 58 Character objects