Что это такое
Паттерн Приспособленец (Flyweight) экономит память, когда программа держит тысячи похожих объектов: тяжёлая разделяемая часть хранится один раз, и на неё ссылаются все, а каждый объект хранит только свою небольшую уникальную часть.
Лесу из 10 000 деревьев не нужно 10 000 текстур — нужны 2 вида деревьев и 10 000 крошечных записей (x, y, вид).
Пример: лес с разделяемыми видами
KindCache::get() выдаёт разделяемые объекты видов, создавая каждый только при первом запросе. Каждое дерево хранит лишь позицию и ссылку на свой вид.
Много деревьев, мало видов
Четыре дерева, но «создаётся вид» печатается лишь дважды — oak и pine существуют по одному экземпляру, каким бы большим ни стал лес. Объекты в PHP передаются по ссылке-дескриптору, поэтому каждое дерево вида oak указывает на один и тот же TreeKind.
Вывод: PHP-скрипт
<?php
// Тяжёлая разделяемая часть — создаётся один раз на каждый вид.
class TreeKind {
public function __construct(
public string $name,
public string $texture,
) {}
}
class KindCache {
/** @var array<string, TreeKind> */
private static array $kinds = [];
public static function get(string $name): TreeKind {
if (!isset(self::$kinds[$name])) {
echo "создаётся вид: ", $name, "\n";
self::$kinds[$name] = new TreeKind($name, $name . ".png");
}
return self::$kinds[$name];
}
public static function count(): int {
return count(self::$kinds);
}
}
// Легковесная часть каждого дерева: позиция и ссылка.
class Tree {
public function __construct(
public int $x,
public int $y,
public TreeKind $kind,
) {}
}
$forest = [
new Tree(1, 2, KindCache::get("oak")),
new Tree(3, 4, KindCache::get("pine")),
new Tree(5, 6, KindCache::get("oak")),
new Tree(7, 8, KindCache::get("pine")),
];
echo "деревьев посажено: ", count($forest), "\n";
echo "видов сохранено: ", KindCache::count(), "\n";
Когда применять
Применяйте, когда профилирование показывает проблемы с памятью из-за множества объектов, повторяющих тяжёлые данные — плитки карты, глифы, частицы, кэшированные стили.
Не применяйте, если объектов меньше тысяч или разделяемая часть невелика. Это паттерн оптимизации: без проблемы с памятью он даёт только сложность.
Плюсы и минусы
Плюсы: большая экономия памяти на масштабе; разделяемые данные обновляются в одном месте.
Минусы: разделяемые объекты нужно считать доступными только для чтения — одна неосторожная запись изменит «каждый дуб в лесу»; разделение на разделяемую и уникальную части усложняет модель.