在Golang开发中,当业务场景需要大量创建具有部分相同属性的对象时,重复创建这些对象会导致内存占用过高、垃圾回收压力增大,进而影响程序性能。享元模式通过分离对象的可变状态与不可变状态,共享不可变状态的对象实例,能够有效减少对象创建数量,降低内存开销。

享元模式核心概念
享元模式包含三个核心角色:
- 享元接口:定义享元对象需要实现的公共方法,是所有享元对象需要遵循的规范。
- 具体享元:实现享元接口,包含对象的不可变内部状态,这些状态是可以被共享的。
- 享元工厂:负责创建和管理享元对象,当请求获取享元对象时,先检查缓存中是否存在,存在则返回已有实例,不存在则创建新实例并加入缓存。
Golang实现享元模式示例
下面以文本编辑器中的字符样式管理为例,字符的字体、大小属于不可变状态可以共享,字符的具体内容属于可变状态由外部传入。
定义享元接口与具体享元
package main
// 享元接口,定义获取样式信息的方法
type CharacterStyle interface {
GetStyleInfo() string
}
// 具体享元,包含可共享的样式信息
type ConcreteStyle struct {
fontFamily string // 字体
fontSize int // 字号
}
// 实现享元接口的方法
func (c *ConcreteStyle) GetStyleInfo() string {
return "字体:" + c.fontFamily + ",字号:" + string(c.fontSize)
}实现享元工厂
// 享元工厂,管理所有享元实例
type StyleFactory struct {
styles map[string]CharacterStyle
}
// 初始化享元工厂
func NewStyleFactory() *StyleFactory {
return &StyleFactory{
styles: make(map[string]CharacterStyle),
}
}
// 获取享元对象的方法,key为样式的唯一标识
func (f *StyleFactory) GetStyle(fontFamily string, fontSize int) CharacterStyle {
key := fontFamily + string(fontSize)
// 检查缓存中是否存在该样式
if style, ok := f.styles[key]; ok {
return style
}
// 不存在则创建新实例并加入缓存
newStyle := &ConcreteStyle{
fontFamily: fontFamily,
fontSize: fontSize,
}
f.styles[key] = newStyle
return newStyle
}外部状态与业务使用
import "fmt"
// 字符结构体,包含享元对象和可变外部状态
type Character struct {
style CharacterStyle // 享元对象,不可变状态
content string // 字符内容,可变外部状态
}
// 打印字符信息
func (c *Character) Print() {
fmt.Printf("字符内容:%s,样式:%s\n", c.content, c.style.GetStyleInfo())
}
func main() {
factory := NewStyleFactory()
// 创建多个字符,共享相同的样式实例
char1 := &Character{
style: factory.GetStyle("宋体", 12),
content: "A",
}
char2 := &Character{
style: factory.GetStyle("宋体", 12),
content: "B",
}
char3 := &Character{
style: factory.GetStyle("黑体", 14),
content: "C",
}
char1.Print()
char2.Print()
char3.Print()
// 验证相同样式的实例是否共享
style1 := factory.GetStyle("宋体", 12)
style2 := factory.GetStyle("宋体", 12)
fmt.Printf("两个宋体12号样式是否为同一实例:%v\n", style1 == style2)
}享元模式的适用场景
- 系统中存在大量相似对象,且这些对象可以拆分为可共享的不可变状态和不可共享的可变状态。
- 对象的大多数状态都可以外部化,仅保留少量内部状态用于存储共享信息。
- 需要大量重复创建对象,且对象创建开销较高,对内存使用比较敏感的场景。
使用注意事项
- 享元模式适合对象数量多、状态可拆分的情况,如果对象数量少或者状态拆分成本高,反而会提升代码复杂度,得不偿失。
- 享元工厂的缓存需要做好并发控制,如果是在并发场景下使用,需要给获取享元的方法加锁,或者使用
sync.Map替代普通的map来管理缓存。 - 要清晰区分对象的内部状态和外部状态,内部状态必须是不可变的,外部状态由调用方自行维护,避免共享状态被意外修改。