Golang学习笔记_38——享元模式

news/2025/2/26 13:21:43

Golang学习笔记_35——代理模式
Golang学习笔记_36——装饰器模式
Golang学习笔记_37——外观模式


文章目录

    • 享元模式(Flyweight Pattern)详解
      • 一、核心概念
        • 1. 定义
        • 2. 解决的问题
        • 3. 核心角色
        • 4. 类图
      • 二、特点分析
      • 三、适用场景
        • 1. 文字编辑器
        • 2. 游戏开发
        • 3. 数据库连接池
      • 四、代码示例(Go语言)
        • 场景:游戏子弹系统
      • 五、高级应用
      • 六、与其他模式对比
      • 七、总结


享元模式(Flyweight Pattern)详解

一、核心概念

1. 定义

享元模式是一种结构型设计模式,通过共享对象来有效支持大量细粒度对象的复用,从而减少内存占用和提高性能。其核心在于区分:

  • 内部状态(Intrinsic):可共享的固定属性(如字符编码、颜色配置)
  • 外部状态(Extrinsic):不可共享的运行时上下文(如坐标位置、字体大小)
2. 解决的问题
  • 内存资源浪费:大量相似对象导致内存占用过高
  • 对象创建开销:高频创建销毁对象影响性能
  • 状态分离管理:需要区分对象固定属性与可变属性
3. 核心角色
  1. Flyweight(抽象享元)
    定义对象接口,声明接收外部状态的方法(如Draw(x,y)

  2. ConcreteFlyweight(具体享元)
    实现抽象接口,存储内部状态(如字符编码)

  3. FlyweightFactory(享元工厂)
    创建并管理享元对象池,确保对象复用

  4. Client(客户端)
    维护外部状态并传递给享元对象

4. 类图

<a class=享元模式类图" />

@startuml
class FlyweightFactory {
    - pool: map[string]Flyweight
    + GetFlyweight(key): Flyweight
}

interface Flyweight {
    + Operation(extrinsicState)
}

class ConcreteFlyweight {
    - intrinsicState: string
    + Operation(extrinsicState)
}

FlyweightFactory o--> Flyweight : manages
Flyweight <|.. ConcreteFlyweight
@enduml

二、特点分析

优点

  1. 内存优化:减少重复对象的存储开销
  2. 性能提升:降低对象创建/销毁频率
  3. 状态解耦:清晰分离内部/外部状态

缺点

  1. 复杂度增加:需要严格区分内外状态
  2. 线程安全问题:共享对象需考虑并发访问
  3. 过度优化风险:不适合对象差异大的场景

三、适用场景

1. 文字编辑器
  • 内部状态:字符编码、字体样式
  • 外部状态:坐标位置、颜色
  • 效果:10万个’A’字符只需1个享元对象
2. 游戏开发
  • 内部状态:子弹贴图、伤害值
  • 外部状态:发射位置、移动轨迹
  • 效果:百万子弹共享10种类型配置
3. 数据库连接池
  • 内部状态:连接配置参数
  • 外部状态:当前事务状态
  • 效果:复用连接避免重复创建

四、代码示例(Go语言)

场景:游戏子弹系统

示例类图

@startuml

class BulletType {
    - name: string
    - color: string
    - texture: []byte
    + String(): string
}

class BulletFactory {
    - pool: map[string]*BulletType
    - mu: sync.Mutex
    + GetBulletType(name: string, color: string): *BulletType
}

class Bullet {
    - typeInfo: *BulletType
    - x: float64
    - y: float64
    - speed: float64
    + String(): string
}

BulletFactory "1" o-- "many" BulletType : contains > pool
Bullet --> BulletType : typeInfo >

note left of BulletFactory
    <<Singleton>>
    Use GetBulletFactory() method
    to get the single instance
end note

@enduml
package flyweight_demo

import (
	"fmt"
	"sync"
)

// BulletType 享元对象 子弹类型(内部状态)
type BulletType struct {
	name    string
	color   string
	texture []byte
}

func (bt *BulletType) String() string {
	return fmt.Sprintf("%s-%s", bt.name, bt.color)
}

// 享元工厂
type BulletFactory struct {
	pool map[string]*BulletType
	mu   sync.Mutex
}

var instance *BulletFactory
var once sync.Once

func GetBulletFactory() *BulletFactory {
	once.Do(func() {
		instance = &BulletFactory{
			pool: make(map[string]*BulletType),
		}
	})
	return instance
}

func (bf *BulletFactory) GetBulletType(name, color string) *BulletType {
	key := name + "-" + color
	bf.mu.Lock()
	defer bf.mu.Unlock()

	if bt, ok := bf.pool[key]; ok {
		return bt
	}

	newBt := &BulletType{
		name:    name,
		color:   color,
		texture: make([]byte, 1024*1024),
	}
	bf.pool[key] = newBt
	return newBt
}

// 具体子弹对象(包含外部状态)
type Bullet struct {
	typeInfo *BulletType
	x, y     float64
	speed    float64
}

func newBullet(name, color string, x, y, speed float64) *Bullet {
	factory := GetBulletFactory()
	return &Bullet{
		typeInfo: factory.GetBulletType(name, color),
		x:        x,
		y:        y,
		speed:    speed,
	}
}

func (b *Bullet) String() string {
	return fmt.Sprintf("%s @(%.1f,%.1f) speed=%.1f",
		b.typeInfo, b.x, b.y, b.speed)
}

func test() {
	bullets := make([]*Bullet, 0)

	// 创建10000发子弹,实际只生成2种BulletType
	for i := 0; i < 5000; i++ {
		bullets = append(bullets,
			newBullet("AK47", "gold", float64(i), 0, 10),
			newBullet("M4A1", "silver", float64(i), 10, 12),
		)
	}

	// 验证对象复用
	fmt.Printf("Total bullet types created: %d\n",
		len(GetBulletFactory().pool)) // 输出2
	fmt.Println(bullets[0].String()) // AK47-gold @(0.0,0.0) speed=10.0
	fmt.Println(bullets[1].String()) // M4A1-silver @(0.0,10.0) speed=12.0
}

=== RUN   Test_test
Total bullet types created: 2
AK47-gold @(0.0,0.0) speed=10.0
M4A1-silver @(0.0,10.0) speed=12.0
--- PASS: Test_test (0.00s)
PASS

五、高级应用

1. 组合享元模式
type WeaponSkin struct {
    baseType *BulletType
    sticker  string // 扩展装饰属性
}

func (ws *WeaponSkin) GetKey() string {
    return ws.baseType.name + "_" + ws.baseType.color + "_" + ws.sticker
}
2. 线程安全优化
// 使用RWMutex提升并发性能
func (bf *BulletFactory) GetBulletTypeSafe(name, color string) *BulletType {
    key := name + "_" + color
    
    // 先尝试读锁
    bf.mu.RLock()
    if bt, exists := bf.pool[key]; exists {
        bf.mu.RUnlock()
        return bt
    }
    bf.mu.RUnlock()
    
    // 获取写锁
    bf.mu.Lock()
    defer bf.mu.Unlock()
    
    // 双检锁防止重复创建
    if bt, exists := bf.pool[key]; exists {
        return bt
    }
    
    newBt := createNewBulletType(name, color)
    bf.pool[key] = newBt
    return newBt
}

六、与其他模式对比

模式核心目标关键区别
单例模式控制实例数量享元管理多个共享对象
对象池模式复用昂贵对象享元侧重状态分离
装饰器模式动态扩展功能享元侧重对象复用

七、总结

享元模式通过共享细粒度对象,有效解决了:

  1. 内存占用问题:减少重复对象的存储
  2. 性能优化问题:降低对象创建开销
  3. 状态管理问题:明确区分内外状态

在Go语言中实现时需注意:

  • 使用sync.Mapmutex保证线程安全
  • 严格分离内部/外部状态
  • 合理设计对象键值(如颜色+名称组合键)

实际应用场景建议:

  • 游戏开发中的粒子系统
  • GUI系统的控件复用
  • 金融交易中的报价对象池

http://www.niftyadmin.cn/n/5868753.html

相关文章

R 语言科研绘图第 27 期 --- 密度图-分组

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

从 Linux 服务器到前端到网关到后端业务逻辑的分析

前言 在现代 Web 应用程序的架构中&#xff0c;一个完整的请求处理流程涉及多个组件&#xff0c;涵盖了用户界面、服务器环境、网关层和后端业务逻辑。理解这一过程有助于优化系统性能、提高用户体验&#xff0c;并确保系统的可维护性和可扩展性。本文将详细分析从 Linux 服务…

机器学习(部分算法、模型)

一、KNN 算法 原理 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;,根据K个邻居样本的类别来判断当前样本的类别; 如果一个样本在特征空间中的k个最相似(最邻近)样本中的大多数属于某个类别&#xff0c;则该类本也属于这个类别 比如: 有10000个样…

Android开发奇葩bug:布局宽高不自动自适应了

Android开发奇葩bug:布局宽高不自动自适应了 写着写着约束布局ConstraintLayout的子控件一初始化过宽高后&#xff0c;后面就算你内容再多&#xff0c;高已经变了&#xff0c;但是它没生效。不管怎么检查代码够感觉没错。 奇怪了 差点我界面就打算重写了。 解决方案&#xff…

Spring 源码硬核解析系列专题(五):Spring Boot 自动装配的原理

在前四期及扩展篇中,我们深入探讨了 Spring 的 IoC 容器、Bean 创建、AOP 和事务管理,这些是 Spring 框架的基石。而 Spring Boot 作为 Spring 的进化版,通过自动装配大幅简化了开发流程。本篇将聚焦 Spring Boot 的自动装配机制,揭秘其如何通过源码实现“约定优于配置”的…

docker部署GPU环境

使用 Docker 部署 GPU 环境涉及到几个关键步骤,以下是详细步骤: 1. 安装 NVIDIA 驱动程序 确保你的系统已经安装了 NVIDIA GPU 驱动。这是使用 GPU 的前提条件。 2. 安装 Docker 和 nvidia-container-toolkit 首先,确保你已经安装了 Docker。然后,安装 NVIDIA Containe…

关于vue中el-date-picker type=daterange日期不回显的问题

在构建现代化的前端应用时&#xff0c;使用Element UI框架的el-date-picker组件可以帮助我们快速实现日期选择功能。然而&#xff0c;在处理日期范围选择&#xff08;daterange&#xff09;时&#xff0c;可能会遇到日期数据从后端获取并试图回显到前端界面时出现的问题。 一、…

HPE Aruba Networking推出全新解决方案助力零售商增强物联网数据收集与边缘处理能力

全新网络连接解决方案助力IT 团队加强对零售环境的保护与管理,提升物联网(IoT)安全性,同时优化用户体验与运营效率 纽约 — 2025年2月25日 —慧与科技(NYSE: HPE)日前宣布推出全新功能,借助高效的网络连接和高性能边缘计算,助力零售商提升客户体验与运营效率,从而进一步打造零…