《golang设计模式》第二部分·结构型模式-06-享元模式(Flyweight)

2023-09-19 10:42:17

1. 概述

享元(Flyweight)模式采用共享方式向客户端提供数量庞大的细粒度对象。

所谓细粒度对象,是指实现了业务细节并相互独立的对象。细粒度对象是一种相对概念,一般不会进行更小粒度的拆分。

1.1角色

  • 抽象享元(Flyweight):
    • 通常是一个接口或抽象类
    • 它声明了具体享元类的公共方法
  • 具体享元(Concrete Flyweight)
    • 实现了抽象享元
    • 包含内部状态和外部状态
      • 内部状态:不可以被改变
      • 外部状态:可以被改变
    • 通常每一个具体享元类提供唯一的享元对象(可参考单例模式)
  • 享元工厂(Flyweight Factory):
    • 负责创建和管理享元角色
    • 它是享元的聚合,通常为:HashMap[key]Flyweight

当客户对象请求一个享元 对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在 的话,则创建一个新的享元对象。

1.2 类图

2. 代码示例

2.1 设计

  • 定义一个抽象棋子(抽象享元)
  • 它的实现是 黑子、白子两个实际享元
    • 它的不变部分:颜色、形状
      • 它的Create()方法用来设置这些不可变部分
    • 它的可变部分:位置
      • 它的Set()方法用来设置位置
  • 定义一个享元工厂
    • 它应该包含两个享元
      • 1号享元黑子
      • 2号享元白子
    • 它的Get方法用来生产棋子
      • 如果有需要的棋子则返回查到的结果
      • 如果没有需要的棋子则创建该棋子
  • 调用
    • 创建一个享元工厂
    • 下第一颗子
      • 用享元工厂Get()方法实例化一个黑子
        • 此时享元工厂中没有黑子享元,它会创建一个黑子享元并返回。(当然这个过程用户看不出来,想看的话你可以在这之前打印享元工厂或者调试)
      • 用黑子的Set()方法设置这颗棋子的位置
      • 查看结果
    • 下第二颗子,也是白子的第一颗子
      • 用享元工厂Get()方法实例化一个白子

        • 此时享元工厂中没有白子享元,它会创建一个白子享元并返回。(当然这个过程用户看不出来,想看的话你可以在这之前打印享元工厂或者调试)
      • 用黑子的Set()方法设置这颗棋子的位置

      • 查看结果

    • 下第三颗子,也是黑子的第三颗子
      • 用享元工厂Get()方法实例化一个黑子
        • 此时享元工厂已经有黑子享元,它会直接返回这个享元。(当然这个过程用户看不出来,想看的话你可以在这之前打印享元工厂或者调试)
      • 用黑子的Set()方法设置这颗棋子的位置
      • 查看结果

2.2 代码

package main

import (
	"errors"
	"fmt"
)

//定义一个抽象享元
type Flyweight interface {
	Create()
	Set(x int64, y int64)
	Get()
}
//定义一个实际享元(白色棋子)
type ConcreteFlyweightA struct {
    //它的不变部分
	Color string
	Shape string
	//它的可变部分
	PostX int64
	PostY int64
}
//定义设置不变部分的方法(设置棋子本身)
func (c *ConcreteFlyweightA) Create() {
	c.Color = "黑色"
	c.Shape = "圆形"
}
//定义设置可变部分的方法(棋子的位置)
func (c *ConcreteFlyweightA) Set(x int64, y int64) {
	c.PostX = x
	c.PostY = y
}
//查看方法
func (c *ConcreteFlyweightA) Get() {
	fmt.Printf("%+v\n", c)
}
//定义另一个具体享元(黑子),和第一个类似
type ConcreteFlyweightB struct {
	Color string
	Shape string
	PostX int64
	PostY int64
}
//定义设置不变部分的方法(设置棋子本身)
func (c *ConcreteFlyweightB) Create() {
	c.Color = "白色"
	c.Shape = "圆形"
}
//定义设置可变部分的方法(棋子的位置)
func (c *ConcreteFlyweightB) Set(x int64, y int64) {
	c.PostX = x
	c.PostY = y
}
//查看方法
func (c *ConcreteFlyweightB) Get() {
	fmt.Printf("%+v\n", c)
}
//定义享元工厂,它包含所需的享元且用key标识
type FlyweightFactory struct {
	Flyweights map[int64]Flyweight
}
//定义创建享元的方法。如果找到就返回,找不到就创建
func (f *FlyweightFactory) Get(id int64) (flyweight Flyweight, err error) {
	flyweight, ok := f.Flyweights[id]
	if ok {
		return flyweight, nil
	} else {
		switch id {
		case 1:
			flyweight = &ConcreteFlyweightA{}
			flyweight.Create()
			f.Flyweights[1] = flyweight
			return flyweight, nil
		case 2:
			flyweight = &ConcreteFlyweightB{}
			flyweight.Create()
			f.Flyweights[2] = flyweight
			return flyweight, nil
		default:
			errors.New("id无效")
			return nil, err
		}
	}
}

func main() {
    //实例化一个享元工厂
	flyweightFactory := FlyweightFactory{
		Flyweights: make(map[int64]Flyweight),
	}
	fmt.Println("=========黑棋落第一个子=========")
	//实例化第一个黑子,因为享元工厂中没有复合条件的棋子,因此会创建一个享元返回
	flyweight001, err := flyweightFactory.Get(1)
	if err != nil {
		return
	}
	//设置该棋子的位置
	flyweight001.Set(3, 3)
	//查看结果
	flyweight001.Get()

	fmt.Println("=========白棋落第一个子=========")
	//实例化第一个白子,因为享元工厂中没有复合条件的棋子,因此会创建一个享元返回
	flyweight002, err := flyweightFactory.Get(2)
	if err != nil {
		return
	}
	//设置该棋子的位置
	flyweight002.Set(17, 4)
	//查看结果
	flyweight002.Get()

	fmt.Println("=========黑棋落第二个子=========")
	//实例化第二个黑子,此时享元工厂有复合条件的享元,返回查到的结果(但是我们封装到工厂里,用户并不能察觉是创建还是复制)
	flyweight003, err := flyweightFactory.Get(1)
	if err != nil {
		return
	}
	//设置该棋子的位置
	flyweight003.Set(3, 17)
	//查看结果
	flyweight003.Get()
}
  • 输出
=========黑棋落第一个子=========
&{Color:黑色 Shape:圆形 PostX:3 PostY:3} 
=========白棋落第一个子=========
&{Color:白色 Shape:圆形 PostX:17 PostY:4}
=========黑棋落第二个子=========
&{Color:黑色 Shape:圆形 PostX:3 PostY:17}

2.3 类图示例

这里我们的客户端实际是调用了Flyweight接口
当然我们也可以如示例中一样直接调用每一个实际享元


在这里插入图片描述

更多推荐

【Java 基础篇】Java TCP通信详解

TCP(TransmissionControlProtocol)是一种面向连接的、可靠的网络传输协议,它提供了端到端的数据传输和可靠性保证。TCP通信适用于对数据传输的可靠性和完整性要求较高的场景,如文件传输、网页浏览等。本文将详细介绍Java中如何使用TCP协议进行网络通信,包括TCP套接字、服务器和客户端的创建、数

麒麟信安的2023世界计算大会时刻

9月15至16日,由工业和信息化部、湖南省人民政府主办的2023世界计算大会在长沙隆重举行。麒麟信安连续五年亮相世界计算大会,本届大会麒麟信安作为计算产业的重要建设者、国家新一代自主安全计算系统产业集群内核心企业,在展览展示、主题演讲、工控操作系统创新研究院揭牌仪式等多环节中深度参与。大会以“计算万物湘约未来——计算产

Leetcode算法入门与数组丨3. 数组基础

文章目录前言1数组简介2数组的基本操作2.1访问元素2.2查找元素2.3插入元素2.4改变元素2.5删除元素3总结task03task04前言Datawhale组队学习丨9月Leetcode算法入门与数组丨打卡笔记这篇博客是一个入门型的文章,主要是自己学习的一个记录。内容会参考这篇笔记(很详细):LeetCode算法笔

Python中的3D矩阵操作

迷途小书童读完需要6分钟速读仅需2分钟3D矩阵又称为立体矩阵,是指一个具有三个维度的矩阵结构。相比二维矩阵,它增加了一个深度维度。在3D矩阵中,第一个维度表示行数,第二个维度表示列数,第三个维度表示层数或深度,可以想象成一个多层的立方体结构。三维矩阵通常也称为NxNxN矩阵,在计算机视觉、医学成像、深度学习、增强现实等

Unity使用Mirror制作局域网的同步

1.脚本布置.参考tank那个demo制作1.新建空物体,为管理脚本的物体:manager,挂载NetworkManager,kcpTransport,NetworkManagerHud.2.设置玩家出生点,spawnPoint,设置好初始化的position的位置(*),挂载NetworkStartPosition的

Cortex-M3/M4基础

一、Cortex-M3/M4通用寄存器1、我们首先来了解一下M3/M4的寄存器,M4比M3多了一个浮点单元FPU。其他的部分基本和M3是一样的。2、Cortex-M3/M4系列处理器拥有通用寄存器R0-R15以及一些特殊功能的寄存器。3、R0‐R12是最“通用目的”的。4、但是绝大多数的16位指令只能使用R0‐R7(低

学习JVM调优

学习JVM调优是为了优化Java应用程序的性能和资源利用。本文将从以下几个方面详细介绍学习JVM调优的步骤和技巧,帮助读者更好地理解和应用这些调优技术。第一部分:理解JVM在学习JVM调优之前,我们需要先理解JVM的工作原理和内部机制。Java虚拟机是Java程序运行的环境,它负责将Java字节码转换为机器代码并运行。

docker学习:dockerfile和docker-compose

学习如何使用dockerfile以下内容,部分来自gpt生成,里面的描述可能会出现问题,但代码部分,我都会进行测试。1.需求对于一个docker,例如python,我们需要其在构建成容器时,就有np。有以下两种方法:pullpython,并run后,在里面pipinstallnumpy,随后对这个容器进行打包保存在pu

如何使用微信文件传输助手?看这里!

微信文件传输助手在哪里?为什么我找不到?有哪位朋友能够告诉我吗?微信文件传输助手是微信官方推出的一款辅助工具,为用户提供了便捷的文件传输方式。用户在使用微信的过程中,可以随时随地通过该功能在手机和电脑之间任意传输照片、视频以及文件。但是有些朋友可能不知道微信文件传输助手怎么使用,接下来,就让小编带大家看看如何使用微信文

单中的部分字段失去焦点后,将数据还原为进入弹窗时的接口数据

要实现在表单中的部分字段失去焦点后,将数据还原为进入弹窗时的接口数据,可以在进入弹窗时将接口数据保存为一个备份,然后在失去焦点的事件处理函数中将字段值设置为备份数据中相应字段的值。如果this.form.originalData的值被同步修改,原因可能是因为JavaScript中的对象是引用类型。当你将一个对象赋值给另

sed命令在Mac和Linux下的不同

问题(1)Windows系统里,文件每行结尾是'<回车><换行>','\r\n'(2)Mac系统里,文件每行结尾是'<回车>',即'\r'(3)Unix系统里,文件每行结尾是'<换行>',即'\n'所以,用'\n'作为作为换行符的文件,用Windows的记事本打开时会没有换行;而用'\r\n'作为换行符的文件(wind

热文推荐