结构型模式-享元模式

什么是享元模式

享元是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中  载入更多对象

  1. 享元模式只是一种优化。在应用该模式之前,你要确定程序 中存在与大量类似对象同时占用内存相关的内存消耗问题, 并且确保该问题无法使用其他更好的方式来解决

  2. . 享元(Flyweight)类包含原始对象中部分能在多个对象中共 享的状态。同一享元对象可在许多不同情景中使用。享元中 存储的状态被称为“内在状态”。传递给享元方法的状态被 称为“外在状态“

  3. 情景(Context)类包含原始对象中各不相同的外在状态。情 景与享元对象组合在一起就能表示原始对象的全部状态。

  4. 通常情况下,原始对象的行为会保留在享元类中。因此调用 享元方法必须提供部分外在状态作为参数。但你也可将行为 移动到情景类中,然后将连入的享元作为单纯的数据对象

  5. 客户端(Client)负责计算或存储享元的外在状态。在客户 端看来,享元是一种可在运行时进行配置的模板对象,具体 的配置方式为向其方法中传入一些情景数据参数

  6. 享元工厂(Flyweight Factory)会对已有享元的缓存池进行 管理。有了工厂后,客户端就无需直接创建享元,它们只需 调用工厂并向其传递目标享元的一些内在状态即可。工厂会 根据参数在之前已创建的享元中进行查找,如果找到满足条 件的享元就将其返回;如果没有找到就根据参数新建享元。

Example

享元工厂

package main

import "fmt"

const (
//TerroristDressType terrorist dress type
TerroristDressType = "tDress"
//CounterTerrroristDressType terrorist dress type
CounterTerrroristDressType = "ctDress"
)

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 == TerroristDressType {
d.dressMap[dressType] = newTerroristDress()
return d.dressMap[dressType], nil
}
if dressType == CounterTerrroristDressType {
d.dressMap[dressType] = newCounterTerroristDress()
return d.dressMap[dressType], nil
}

return nil, fmt.Errorf("Wrong dress type passed")
}

func getDressFactorySingleInstance() *DressFactory {
return dressFactorySingleInstance
}

享元接口

package main

type Dress interface {
getColor() string
}

享元对象A

package main

type TerroristDress struct {
color string
}

func (t *TerroristDress) getColor() string {
return t.color
}

func newTerroristDress() *TerroristDress {
return &TerroristDress{color: "red"}
}

享元对象B

package main

type CounterTerroristDress struct {
color string
}

func (c *CounterTerroristDress) getColor() string {
return c.color
}

func newCounterTerroristDress() *CounterTerroristDress {
return &CounterTerroristDress{color: "green"}
}

背景

package main

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
}

客户端A

package main

type game struct {
terrorists []*Player
counterTerrorists []*Player
}

func newGame() *game {
return &game{
terrorists: make([]*Player, 1),
counterTerrorists: make([]*Player, 1),
}
}

func (c *game) addTerrorist(dressType string) {
player := newPlayer("T", dressType)
c.terrorists = append(c.terrorists, player)
return
}

func (c *game) addCounterTerrorist(dressType string) {
player := newPlayer("CT", dressType)
c.counterTerrorists = append(c.counterTerrorists, player)
return
}

客户端

package main

import "fmt"

func main() {
game := newGame()

//Add Terrorist
game.addTerrorist(TerroristDressType)
game.addTerrorist(TerroristDressType)
game.addTerrorist(TerroristDressType)
game.addTerrorist(TerroristDressType)

//Add CounterTerrorist
game.addCounterTerrorist(CounterTerrroristDressType)
game.addCounterTerrorist(CounterTerrroristDressType)
game.addCounterTerrorist(CounterTerrroristDressType)

dressFactoryInstance := getDressFactorySingleInstance()

for dressType, dress := range dressFactoryInstance.dressMap {
fmt.Printf("DressColorType: %s\nDressColor: %s\n", dressType, dress.getColor())
}
}

适用场景

  1. 仅在程序必须支持大量对象且没有足够的内存容量时使用享 元模式
  • 程序需要生成数量巨大的相似对象
  • 这将耗尽目标设备的所有内存
  • 对象中包含可抽取且能在多个对象间共享的重复状态

优缺点

优点

  • 如果程序中有很多相似对象,那么你将可以节省大量内存

缺点

  • 你可能需要牺牲执行速度来换取内存,因为他人每次调用享 元方法时都需要重新计算部分情景数据
  • 代码会变得更加复杂。团队中的新成员总是会问:“为什么 要像这样拆分一个实体的状态

与其他模式的关系

  • 你可以使用享元实现组合树的共享叶节点以节省内存。
  • 享元展示了如何生成大量的小型对象,外观则展示了如何用 一个对象来代表整个子系统。
  • 如果你能将对象的所有共享状态简化为一个享元对象,那么 享元就和单例类似了。但这两个模式有两个根本性的不同。
    • 只会有一个单例实体,但是享元类可以有多个实体,各实 体的内在状态也可以不同。
    • 单例对象可以是可变的。享元对象是不可变的。