Flyweight в TypeScript: общие неизменяемые данные и отдельные координаты
Flyweight разделяет повторяющиеся данные между множеством объектов. Общая часть, intrinsic state, не должна меняться для одного потребителя. Координаты и другое индивидуальное extrinsic state остаются снаружи. Выигрыш возможен, когда дублирование действительно существенно; сам паттерн ускорения не гарантирует.
Фабрика общего описания
type Texture = Readonly<{ color: string; pattern: string }>;
class TextureFactory {
private readonly cache = new Map<string, Texture>();
get(color: string, pattern: string): Texture {
const key = JSON.stringify([color, pattern]);
let texture = this.cache.get(key);
if (!texture) {
texture = Object.freeze({ color, pattern });
this.cache.set(key, texture);
}
return texture;
}
}
type Sprite = { x: number; y: number; texture: Texture };
const factory = new TextureFactory();
const first: Sprite = {
x: 10, y: 20, texture: factory.get("green", "dots"),
};
const second: Sprite = {
x: 70, y: 20, texture: factory.get("green", "dots"),
};
console.log(first.texture === second.texture); // true
first.x = 100;
console.log(second.x); // 70Фабрика хранит один объект для одной пары color/pattern. JSON-массив в ключе не путает пары из-за выбранного вручную разделителя. Map не интерпретирует пользовательский ключ как унаследованное свойство объекта, как могло случиться с обычным объектом-кешем.
Что защищает readonly, а что freeze
Readonly помогает TypeScript обнаружить попытку присвоения при проверке типов. Object.freeze ограничивает изменение свойств во время выполнения. Обе защиты здесь неглубокие; это достаточно для двух строковых полей, но вложенный изменяемый объект не стал бы автоматически неизменяемым.
Проверки перед применением
- Одинаковое intrinsic state возвращает ту же ссылку; разные параметры не должны случайно разделять объект.
- Изменение координат одного Sprite не меняет координаты другого.
- Общие данные не содержат идентификатор пользователя, координаты или другой индивидуальный контекст.
- Фабрика имеет понятный срок жизни. Неограниченный Map с бесконечным набором ключей способен удерживать память дольше самих потребителей.
Пример показывает структуру паттерна, а не измеренную экономию памяти. Профилируйте реальную нагрузку: для крошечных объектов служебные ключи и Map могут стоить больше, чем дублирование. Иногда достаточно обычного кеша или небольшого заранее подготовленного набора ресурсов.