← 返回题目列表

使用享元模式有哪些常见误区?

高频 中等 第 5 / 25 题 更新于 2026/07/28
享元模式误区状态拆分

简化版

享元模式常见误区包括:把它等同于缓存、没有正确拆分内部状态和外部状态、共享可变对象、缓存 key 设计不完整、对象数量不多却过度设计,以及忽略缓存池增长和线程安全问题。

详细版

享元模式的关键不是“放进 Map 缓存”,而是“共享内部状态,外部状态由调用方传入”。

常见错误有:

  • 把完整业务对象直接缓存起来,导致上下文状态串扰。
  • 把坐标、用户、请求参数等外部状态放进享元对象。
  • 共享对象允许修改,影响所有使用方。
  • key 不完整,导致不同内部状态被错误复用。
  • key 太细,导致几乎没有复用收益。
  • 对象数量很少,却引入复杂工厂和对象池。

完整版教学

一、误区一:把享元模式等同于缓存

享元模式常用缓存实现,但缓存不一定是享元。

例如把数据库查询结果缓存起来,主要是避免重复查库。这是缓存思路,不一定涉及内部状态和外部状态拆分。

享元模式关注的是大量相似对象的共享。它缓存的是可共享的内部状态对象。

二、误区二:把外部状态放进享元对象

这是最严重的错误。

例如共享棋子对象保存坐标,共享字体对象保存当前字符位置,共享模板对象保存当前用户 ID,都会导致不同使用场景互相覆盖。

享元对象应该只保存可共享状态。变化状态必须在调用时传入,或者由上下文对象单独维护。

三、误区三:共享可变对象

共享对象如果可变,修改影响会扩散到所有引用处。

例如多个组件共享一个样式对象,某个组件临时修改颜色,其他组件可能也跟着变色。

解决方式是:

  • 享元对象尽量不可变。
  • 不暴露可变字段。
  • 对集合做不可变封装或防御性拷贝。
  • 需要变化的部分放到外部状态。

四、误区四:缓存 key 设计错误

key 太粗会错误复用,key 太细会无法复用。

例如树类型应该由树种、颜色、纹理共同决定。如果只用树种作为 key,就可能把不同颜色的树错误当成同一种。

反过来,如果把坐标也放进 key,每棵树 key 都不同,享元池就失去复用意义。

五、误区五:忽略缓存池膨胀

享元池如果没有边界,可能越来越大。

尤其是 key 来自用户输入、请求参数或动态配置时,享元对象可能持续增加,最终反而造成内存压力。

使用享元前要评估 key 的离散程度。如果复用率低,不适合放入享元池。

六、误区六:小场景中过度设计

如果对象数量很少,或者对象创建成本很低,享元模式可能没有必要。

例如一个页面只有几个固定按钮,强行抽出按钮样式享元工厂,代码会比收益更重。

享元模式适合规模带来的问题,不适合为了模式而模式。

七、误区七:忽略并发安全

享元对象经常被多个线程共享。如果对象可变或工厂不安全,会出现并发问题。

多线程场景下应优先使用不可变享元对象,并用线程安全集合管理对象池。

八、常见误区与追问

享元误用往往把“缓存对象”误当成“共享对象状态”。若10000个棋子的位置被塞进共享棋子实例,不同棋局会互相覆盖;位置必须作为调用参数或上下文传入。只有颜色、纹理等稳定且可复用的内部状态才适合进入享元。

检查维度判定依据
可共享不可变、与使用场景无关的内部状态
不可共享位置、用户、请求进度等外部状态
错误:Flyweight.position = x;正确:draw(x, y)

心法:共享前先问“这个字段换一个使用位置还成立吗”。

  • 误区:加一个 Map 缓存就是享元模式。 缓存关注复用结果,享元还要求拆分内部状态与外部状态。
  • 追问:享元为什么最好不可变? 多处共享可变对象会引发串数据和并发竞争。
  • 误区:所有字段都应移到外部。 稳定且重复的状态留在内部才能获得内存收益。
  • 追问:外部状态太多怎么办? 调用参数会膨胀,应重新评估对象边界或封装独立上下文。
  • 追问:何时共享反而更慢? 对象很少或查找键计算昂贵时,工厂和哈希查询成本可能不划算。

九、加强记忆

享元模式最容易错在“共享了不该共享的东西”。判断时抓住两件事:共享的是内部状态,变化的是外部状态;key 要刚好描述内部状态,多一分少一分都可能出问题。