11. 享元模式(Flyweight)

问题:大量相似对象占用过多内存。

核心:把不变的部分(内在状态)共享,可变的部分(外在状态)外部传入。

class TextStyle {
  constructor(
    public readonly font: string,
    public readonly size: number,
    public readonly color: string,
  ) {}
}

class StyleFactory {
  #cache: Map<string, TextStyle> = new Map();

  getStyle(font: string, size: number, color: string): TextStyle {
    const key = `${font}-${size}-${color}`;
    let style = this.#cache.get(key);
    if (!style) {
      style = new TextStyle(font, size, color);
      this.#cache.set(key, style);
    }
    return style;
  }
}

const factory = new StyleFactory();
const s1 = factory.getStyle('Arial', 14, 'black');
const s2 = factory.getStyle('Arial', 14, 'black');
console.log(s1 === s2); // true — 共享同一个对象
package flyweight

import "fmt"

type TextStyle struct {
  Font  string
  Size  int
  Color string
}

type StyleFactory struct {
  cache map[string]*TextStyle
}

func NewStyleFactory() *StyleFactory {
  return &StyleFactory{cache: make(map[string]*TextStyle)}
}

func (f *StyleFactory) GetStyle(font string, size int, color string) *TextStyle {
  key := fmt.Sprintf("%s-%d-%s", font, size, color)
  if _, ok := f.cache[key]; !ok {
    f.cache[key] = &TextStyle{Font: font, Size: size, Color: color}
  }
  return f.cache[key]
}

实战场景

  • 文本编辑器样式共享:成千上万个字符不必各自保存字体、字号、颜色,只共享同一份样式对象。
  • 地图标记:大量点位共享图标、颜色、交互配置,坐标和业务 id 作为外部状态传入。
  • 游戏粒子和子弹:纹理、模型、动画配置共享,位置、速度、生命周期由外部状态管理。
  • 棋盘或网格单元:单元格的样式、类型、行为共享,当前坐标和状态单独存储。
  • 图标资源池:同一个 SVG、图片或字体图标只加载一次,多个组件引用同一资源。
  • 权限规则对象:大量用户共享同一组角色定义或策略模板,用户自己的角色绑定是外部状态。
  • 表格单元格渲染:大数据表格中,渲染器、格式化器、校验器可以共享,具体值和行号外部传入。
  • CAD 或设计工具:相同组件、符号、物料只保存一份定义,画布上的实例只保存位置、旋转、缩放。
  • 编译器 token 类型:关键字、操作符、标识符类型等元信息共享,具体词面量和位置独立保存。
  • 缓存池:颜色对象、正则表达式、格式化器、Intl formatter 等创建成本高且可复用的对象可以集中共享。