享元模式适用于内在状态稳定且可复用的对象,如弹幕样式、角色图标等;外部状态(如$userid)必须由客户端传入;工厂需避免并发污染和重操作;仅在内存线性增长时才值得引入。

类爆炸不是因为写多了类,而是因为本该复用的对象被反复实例化——享元模式能压住它,但只在真正出现内存线性增长时才值得上。
哪些对象适合抽成享元?看内在状态是否稳定
内在状态必须是只读、可跨请求复用的,比如 $iconType、$roleCode、$textColor;一旦你发现某个属性每次 new 都不同(如 $userId、$createdAt、$position),它就属于外部状态,绝不能放进享元对象里。
- 适合场景:弹幕样式配置、用户角色图标、日志上下文标签、富文本段落格式模板
- 反例:
Flyweight类里有$this->requestId或$this->ip字段 —— 这会让对象失去共享前提 - 验证方法:两个相同 key 创建的享元,用
===比较应返回 true;若不成立,说明内在状态没对齐或被意外修改
工厂怎么建?别让 $pool 变成并发污染源
PHP-FPM 下每个请求是独立进程,static $pool = [] 天然隔离;但如果你跑在 Swoole 或 CLI 长生命周期环境里,static 数组会被多个协程/线程共用,同时写入会丢数据或重复创建。
- 推荐做法:用
WeakMap(PHP 8.0+)管理实例引用,避免内存泄漏 - 兼容方案:加
spl_object_hash($this)辅助去重,或改用Sync::lock()(Swoole)包裹写入逻辑 - 别在工厂里做重操作:比如查 DB 或读文件来构造内在状态 —— 工厂要快,否则它自己就成了瓶颈
- 调试技巧:在
getFlyweight()里加echo "created: $key\n";,上线前确认相同 key 只输出一次
客户端怎么传外部状态?结构化比零散参数更可控
享元对象不存坐标、ID、时间戳,这些全靠调用方传进来。这不是为了“看起来解耦”,而是防止状态归属模糊后演变成维护黑洞。
- 错误写法:
$bullet->fire($x, $y)—— 参数顺序和含义易错,无法扩展 - 正确写法:
$bullet->render(['x' => $x, 'y' => $y, 'power' => $power])—— 键名明确,支持默认值和校验 - 注意 PHP 数组传参开销:如果外部状态字段极多且高频调用,考虑用
stdClass或 DTO 对象缓存,避免每次重建数组 - 别把外部状态塞进全局变量或
$_SESSION—— 享元的复用边界会失控
最常被忽略的一点:享元不是缓存策略,也不是对象池抽象层。它只解决一个具体问题——当你的监控显示对象数量随数据量线性上涨、而堆内存使用率同步飙升时,才该动手拆内外状态。在此之前,加一层工厂、改一堆调用方式,只会让代码更难读懂。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











