Flyweight — sharing common state to save memory
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
// 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