Паттерн Приспособленец (Flyweight) в PHP
Короткий скринкаст: паттерн «Приспособленец» (Flyweight) — разделяем состояние с помощью кэша. Нарисуем лес: тип дерева один на всех, позиции хранятся отдельно.
Описание
Приспособленец экономит память, когда объектов много, а повторяющееся состояние мало. Тип дерева (имя, цвет, текстура) не меняется — фабрика возвращает один объект TreeType на всех. Позиция (x, y) уникальна для каждого дерева и живёт в контексте Tree. Посадили три дерева, а тип создали только два.
UML-диаграмма
Код
TreeType.php
class TreeType
{
public function __construct(
public string $name,
public string $color,
public string $texture,
) {
}
public function render(int $x, int $y): string
{
return "$this->name at $x, $y";
}
}
Tree.php
class Tree
{
public function __construct(
private int $x,
private int $y,
private TreeType $type,
) {
}
public function draw(): string
{
return $this->type->render($this->x, $this->y);
}
}
TreeFactory.php
class TreeFactory
{
private array $types = [];
public function place(int $x, int $y, string $name, string $color, string $texture): Tree
{
$key = "$name|$color|$texture";
if (! isset($this->types[$key])) {
$this->types[$key] = new TreeType($name, $color, $texture);
}
return new Tree($x, $y, $this->types[$key]);
}
public function count(): int
{
return count($this->types);
}
}
demo.php
require_once 'TreeType.php';
require_once 'Tree.php';
require_once 'TreeFactory.php';
$factory = new TreeFactory();
$trees[] = $factory->place(0, 0, 'Birch', 'Green', 'leaf.png');
$trees[] = $factory->place(1, 4, 'Birch', 'Green', 'leaf.png');
$trees[] = $factory->place(5, 2, 'Oak', 'DarkGreen', 'oak.png');
echo 'trees: '.count($trees).PHP_EOL;
echo 'types: '.$factory->count().PHP_EOL;
Пример результата
После php demo.php видно, что деревьев три, а типов два — берёзы разделяют один объект TreeType:
trees: 3
types: 2
Преимущества
- Резко снижает потребление памяти при множестве похожих объектов.
- Общее (внутреннее) состояние хранится один раз и разделяется.
- Дорогие разделяемые данные создаются лениво и кэшируются.
- Клиент продолжает работать с обычными объектами.
Когда использовать
- Когда объектов очень много и память критична.
- Когда большую часть состояния можно вынести наружу как внешнее.
- Когда одинаковые данные повторяются у множества объектов.
- Когда уникальность каждого экземпляра не требуется.
Использование в Laravel
В Laravel Приспособленец — это кэширование дорогих общих данных: скомпилированные Blade-шаблоны (Illuminate\View\Compilers\BladeCompiler) и загруженные переводы (Illuminate\Translation\Translator) создаются один раз и переиспользуются. Конфиг и роуты, собранные командами config:cache и route:cache, — то же общее «внутреннее» состояние, которое разделяется между запросами вместо повторной сборки.