享元模式核心是“共享+不可变+外部化”,即提取不变的内在状态复用,将变化的外在状态由调用方传入;go中通过不可变结构体、sync.Pool管理与参数注入实现。

享元模式的核心思想
享元模式(Flyweight Pattern)本质是“共享+不可变+外部化”。它把对象中可共享的、不变的状态(内在状态)提取出来复用,而将依赖上下文的、变化的部分(外在状态)由调用方传入。在 Go 中,这通常体现为:一个轻量结构体(享元) + 一个对象池(sync.Pool 或自定义缓存) + 外部传参处理差异化逻辑。
Go 中实现享元对象池的三步关键操作
不需要复杂框架,用原生特性就能高效落地:
- 定义不可变享元类型:字段全为基本类型或只读指针(如 String、int、*sync.RWMutex 不推荐,但 *config 可接受),不保存任何请求相关数据;方法只读,不修改自身
- 用 sync.Pool 管理实例:New 字段返回新享元指针,Pool 自动复用;注意 Pool 中对象可能被 GC 清理,适合高频短生命周期场景(如 http 请求处理)
- 外在状态通过函数参数注入:比如享元负责渲染模板,但具体用户 ID、时间戳等由调用方传入;享元内部不存这些值,也不设 setter
一个真实可用的文本格式化享元示例
假设多个服务需频繁格式化日志消息,共用相同的格式规则(前缀、分隔符、时区),但每条日志内容不同:
type LogFormatter Struct { prefix string separator string loc *time.Location // 内在状态:固定不变 } func (f *LogFormatter) Format(msg string, ts time.Time) string { return fmt.Sprintf("%s%s[%s]%s%s", f.prefix, f.separator, ts.In(f.loc).Format("15:04:05"), f.separator, msg) } var formatterPool = sync.Pool{ New: func() interface{} { return &LogFormatter{ prefix: "[app]", separator: " | ", loc: time.UTC, } }, } // 使用时: f := formatterPool.Get().(*LogFormatter) output := f.Format("user login", time.Now()) formatterPool.Put(f) // 归还,供下次复用
注意:Put 前确保享元未被并发修改,否则会破坏不可变性;若需并发安全,享元本身应完全无状态,或用只读字段 + 参数驱动。
立即学习“go语言免费学习笔记(深入)”;
什么时候该用,什么时候别硬套?
享元在 Go 中不是银弹。适用场景很明确:
- 对象创建开销大(如含正则编译、连接初始化),且存在大量重复配置
- 内存敏感,对象数量达万级甚至更多,且多数字段雷同
- 你能清晰区分哪些是内在状态(可共享)、哪些必须每次传入(外在状态)
不适用的情况包括:对象天然唯一(如代表某次请求的 RequestCtx)、状态频繁变更、或复用收益远小于代码复杂度增加——这时 plain struct + sync.Pool 就够了,不必强行套享元术语。
基本上就这些。享元不是语法糖,而是对“复用边界”的一次主动设计。写清楚内在/外在,管住 mutability,池子自然就稳了。