跳到主要内容

享元模式

简介

享元模式(Flyweight Pattern)摒弃了在每个对象中存储所有数据的方式,通过共享多个对象的相同状态,使开发者可以在有限的内存容量中载入更多数据。

享元模式将对象状态划分为两部分:

  • 内在状态(Intrinsic State): 这部分状态是对象共享的,不随环境改变而改变,可以存储在享元对象内部。
  • 外在状态(Extrinsic State): 这部分状态是对象独有的,随环境改变而改变,不能共享,通常由客户端在使用享元对象时传递。

通过将内在状态存储在少量共享的享元对象中,并将外在状态在需要时传递,可以避免创建大量重复的对象,从而有效节省内存。享元模式通常与工厂模式结合使用,由工厂负责管理和提供共享的享元对象。

角色组成

  • 享元(Flyweight),一个接口,定义所有具体享元对象共享的操作方法
  • 具体享元(ConcretFlyweight),实现享元接口的类,包含原始对象中部分能在多个对象中共享的状态。
  • 享元工厂(FlyweightFactory),对已有享元对象的缓存池进行管理的类。客户端通过享元工厂创建具体享元对象,并向其传递目标具体享元对象的一些内部状态。享元工厂会根据参数在之前已创建的享元接口中进行查找,如果找到满足条件的具体享元对象,则将其返回;如果没找到,则根据参数新建具体享元对象。

常见使用场景

享元模式适用于需要创建大量相似对象的场景,特别是当这些对象的内在状态相同,而外在状态不同时。以下是一些常见的应用场景:

  • 文本编辑器中的字符: 在文本编辑器中,每个字符都可以被视为一个对象。但相同字体的同一个字符(如多个“A”)具有相同的内在状态(字体、字号、颜色等),只有其在文档中的位置是外在状态。使用享元模式,可以共享同一个字符的享元对象,只存储每个字符的位置信息,极大地减少内存消耗。
  • 图形或游戏中的对象: 在游戏开发中,可能有大量的相同类型的敌人、树木或子弹。它们拥有相同的基本属性(如外观、动画),但位置、生命值等是不同的。可以将共享属性作为内在状态,非共享属性作为外在状态,使用享元模式来表示这些对象。

优点

  • 节省内存: 这是享元模式最主要的优点。通过共享对象,可以显著减少系统中对象的数量,尤其在需要创建大量细粒度对象时效果更佳。
  • 提高性能: 减少对象的创建和销毁次数,可以降低垃圾回收的压力,从而提高系统的整体性能。

缺点

  • 增加了系统的复杂性: 为了实现享元模式,需要将对象的状态进行拆分,并引入享元工厂来管理共享对象池,这会增加代码的复杂性。
  • 需要区分内在状态和外在状态: 正确地划分对象的内在状态和外在状态是实现享元模式的关键,有时这可能比较困难。
  • 外在状态的管理: 客户端需要负责管理和传递外在状态,这可能会增加客户端的负担。
  • 线程安全问题(在多线程环境下): 如果享元对象是可变的,那么在多线程环境下需要考虑线程安全问题,可能需要加锁或其他同步机制来保证数据的一致性。

示例代码

Simple

package flyweight

import "fmt"

//享元接口
type Flyweight interface {
Operation()
}

// 创建具体享元类,可以共享以支持大型有效的对象数量
type ConcreteFlyweight struct {
intrinsicState string
}

//具体享元对象初始化
func (fw ConcreteFlyweight) Init(intrinsicState string) {
fw.intrinsicState = intrinsicState
}

//具体享元对象的方法
func (fw ConcreteFlyweight) Operation(extrinsicState string) string {
fmt.Println(fw.intrinsicState)
if extrinsicState != "" {
return extrinsicState
}
return "empty extrinsicState"
}

// 创建一个新的具体享元类
func NewConcreteFlyweight(state string) *ConcreteFlyweight {
return &ConcreteFlyweight{state}
}

// 创建用于创建和存储享元的享元工厂类
type FlyweightFactory struct {
pool map[string]*ConcreteFlyweight
}

// 创建一个新的享元工厂对象
func NewFlyweightFactory() *FlyweightFactory {
return &FlyweightFactory{pool: make(map[string]*ConcreteFlyweight)}
}

// 获取或创建具体享元对象
func (f *FlyweightFactory) GetFlyweight(state string) *ConcreteFlyweight {
flyweight, _ := f.pool[state]
if f.pool[state] == nil {
flyweight = NewConcreteFlyweight(state)
f.pool[state] = flyweight
}
return flyweight
}

client

package flyweight

import (
"fmt"
"testing"
)

func TestSimple(t *testing.T) {
factory := NewFlyweightFactory()
fw1 := factory.GetFlyweight("Barry")
fw2 := factory.GetFlyweight("Shirdon")

fmt.Println(fw1.Operation("ok"))
fmt.Println(fw2.Operation("fine"))
}

shirt

比赛中,两个球队各派5名球员比赛,两队球员分别着红色球衣和蓝色球衣。

创建两个服务对象,一个是红队服装对象,它可以在5名红队球员之间共享;蓝队类似。

package flyweight

import "fmt"

// 队员类
type Player struct {
Dress Dress
PlayerType string
lat int
long int
}

// 创建一个队员
func NewPlayer(PlayerType, DressType string) *Player {
Dress, _ := GetDressFactorySingleInstance().GetDressByType(DressType)
return &Player{
PlayerType: PlayerType,
Dress: Dress,
}
}

// 创建队员位置
func (p *Player) NewLocation(lat, long int) {
p.lat = lat
p.long = long
}

// 享元接口, 服装接口
type Dress interface {
GetColor() string
}

const (
//蓝队服装类型
BlueTeamDressType = "Blue Dress"
//红队服装类型
RedTeamDressType = "Red Dress"
)

var (
DressFactorySingleInstance = &DressFactory{
DressMap: make(map[string]Dress),
}
)

// 享元服装工厂
type DressFactory struct {
DressMap map[string]Dress
}

// 获取服装类型
func (d *DressFactory) GetDressByType(DressType string) (Dress, error) {
if d.DressMap[DressType] != nil {
return d.DressMap[DressType], nil
}

if DressType == BlueTeamDressType {
d.DressMap[DressType] = newBlueTeamDress()
return d.DressMap[DressType], nil
}
if DressType == RedTeamDressType {
d.DressMap[DressType] = newRedTeamDress()
return d.DressMap[DressType], nil
}

return nil, fmt.Errorf("%s", "Wrong Dress type")
}

// 获取服装工厂单例
func GetDressFactorySingleInstance() *DressFactory {
return DressFactorySingleInstance
}

// 蓝队服装
type BlueTeamDress struct {
color string
}

func (t *BlueTeamDress) GetColor() string {
return t.color
}

func newBlueTeamDress() *BlueTeamDress {
return &BlueTeamDress{color: "blue"}
}

// 创建红队服装
type RedTeamDress struct {
color string
}

func (c *RedTeamDress) GetColor() string {
return c.color
}

func newRedTeamDress() *RedTeamDress {
return &RedTeamDress{color: "red"}
}

// 创建游戏
type NewGame struct {
}

// 创建蓝队队员
func (ng *NewGame) AddBlueTeam(DressType string) *Player {
return NewPlayer("terrorist", DressType)
}

// 创建红队队员
func (ng *NewGame) AddRedTeam(DressType string) *Player {
return NewPlayer("counterBlueTeam", DressType)
}

client:

package flyweight

import "testing"

func TestShirt(t *testing.T) {
game := &NewGame{}
game.AddBlueTeam(BlueTeamDressType)
game.AddRedTeam(RedTeamDressType)

DressFactoryInstance := GetDressFactorySingleInstance()

for DressType, Dress := range DressFactoryInstance.DressMap {
t.Logf("Dress Type: %s, Color: %s", DressType, Dress.GetColor())
}
}

参考

  • 《Go语言设计模式》