XingPiaoLiang's

Back

外观模式-享元模式#

外观模式#

外观模式是一种结构型模式,能够为程序库、框架或者其他复杂类提供一个简单的接口。这个接口就是该库的门面外观

外观模式的整体思想就是封装,外观类会为包含了许多活动部件的复杂子系统提供一个简单的接口,与直接调用子系统相比,外观模式提供的功能可能很有限,但包含了需求范围内所需要的功能

整体结构#

享元模式#

享元模式是一种结构型模式,它摒弃了在当个对象中存储所有数据的方式,通过共享多个对象所共有的 相同的状态(数据) 能够让我们在有限的内存容量中载入更多的对象。

在一个对象类中,会存在着常量数据和其他的成员变量。常量数据对于所有实例对象都是完全一致的,但其他的变量可能会随着程序的运行不断地变化。

对象的常量数据被称为内在状态,他们只能被外部所访问而不能被外部所修改,而其他状态通常是通过被其它对象从外部进行不断改变的,所以称其为外部状态

整体思想#

享元模式推崇内在状态与外在状态抽离。它建议不在对象中存储外在状态,程序只是在对象中存储生命周期内不变的内在状态,以达到一个对象在不同的场景下都可以被有效复用的效果。因为这些对象的区别仅仅在于其内在状态,相对于外在状态变体会减少很多,自然而然地,对象数量减少了很多,内存占用也就少了很多。

存储外在状态#

外在状态从对象中抽离之后,在大部分情况下都会放在在我们应用享元模式之前的聚合类中。聚合类中需要存在多个数组成员变量,包括对享元对象的引用、外部状态的存储以及两对象之间相应的索引。

当然也还存在着更优雅的方法,那就是创建独立的场景类,用来存储外在状态和享元对象的引用。这样在原本的容器对象中就只需要包含一个数组

代码示例#

本例中,使用享元模式能够有效的减少在画布上渲染树对象的内存。

  • Tree作为树对象
  • TreeTypeFactory享元对象工厂类,在其中获取享元对象
type Tree struct {
	X    string
	Y    string
	Type TreeType
}

type TreeType struct {
	Height int
	Color  string
}

// key ????
type TreeTypeFactory struct {
	types  map[TreeType]int
	Hit    int
	NotHit int
}

func (tf *TreeTypeFactory) GetTypes(height int, color string) TreeType {
	for k := range tf.types {
		if k.Height == height && k.Color == color {
			fmt.Println("Get type from the flyweight factory ")
			tf.Hit += 1
			return k
		}
	}
	treeType := TreeType{
		Height: height,
		Color:  color,
	}
	tf.NotHit += 1
	fmt.Println("Get a New Type")
	tf.types[treeType] = 1
	return treeType
}

func (t *Tree) Plant() {
	fmt.Printf("%#v\n", t)
}

func Test_Flyweight(t *testing.T) {
	height := []int{
		1, 2, 3, 4, 5, 6, 7,
	}
	color := []string{
		"blue", "yellow", "white", "black", "gray",
	}
	factory := &TreeTypeFactory{
		types:  make(map[TreeType]int),
		Hit:    0,
		NotHit: 0,
	}
	for i := 1; i < 100; i++ {
		height = append(height, 1)
	}

	for i := 0; i < 100; i++ {
		colorRandom := rand.Intn(5)
		heightRandom := rand.Intn(100)
		treeType := factory.GetTypes(height[heightRandom], color[colorRandom])
		tree := &Tree{
			X:    strconv.Itoa(heightRandom),
			Y:    strconv.Itoa(colorRandom),
			Type: treeType,
		}
		tree.Plant()
	}
	fmt.Println("Hit", factory.Hit)
	fmt.Println("NotHit", factory.NotHit)
}
go

随机创建一百次树实现结果如下:

Get type from the flyweight factory
&test.Tree{X:"98", Y:"1", Type:test.TreeType{Height:1, Color:"yellow"}}
Get type from the flyweight factory
&test.Tree{X:"23", Y:"4", Type:test.TreeType{Height:1, Color:"gray"}}
Get type from the flyweight factory
&test.Tree{X:"96", Y:"1", Type:test.TreeType{Height:1, Color:"yellow"}}
Get a New Type
&test.Tree{X:"5", Y:"3", Type:test.TreeType{Height:6, Color:"black"}}
Get type from the flyweight factory
&test.Tree{X:"43", Y:"0", Type:test.TreeType{Height:1, Color:"blue"}}
Get type from the flyweight factory
&test.Tree{X:"68", Y:"3", Type:test.TreeType{Height:1, Color:"black"}}
Get type from the flyweight factory
&test.Tree{X:"93", Y:"1", Type:test.TreeType{Height:1, Color:"yellow"}}
Get type from the flyweight factory
&test.Tree{X:"48", Y:"4", Type:test.TreeType{Height:1, Color:"gray"}}

Hit 87
NotHit 13
bash

应用场景#

  • 在程序必须支持大量对象并且没有足够的内存容量的时候使用享元模式。

How To Implement#

  1. 将需要改写为享元的类成员变量拆分为两个部分:
  • 内在状态: 包含不变的、 可在许多对象中重复使用的数据的成员变量。
  • 外在状态: 包含每个对象各自不同的情景数据的成员变量
  1. 保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。 这些变量仅可在构造函数中获得初始数值。

  2. 找到所有使用外在状态成员变量的方法, 为在方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量。

  3. 你可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂。

  4. 客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。

二者对比#

  • 享元展示了生成大量的小型对象,外观展示了使用一个对象代表整个子系统
  • 单例对象是可变的,享元对象是不可变的.
设计模式-结构型-外观-享元
https://astro-pure.js.org/blog/flyweight_facade
Author erasernoob
Published at May 28, 2025
Comment seems to stuck. Try to refresh?✨