Python 设计模式之享元模式

news2024/9/21 18:48:01

文章目录

    • 从一个 MP3 案例谈起
    • flyweight 模式解决
      • flyweight pattern 的组件
      • 拆解定义与逐步实现
      • 完整代码
    • 未讨论问题

享元模式( flyweight pattern)属于结构型设计模式,主要用于解决系统中大量创建同一个类的实例时导致的内存激增的问题,它的解决思路是将类的实例属性拆分成外部属性和内部属性。

  • 外部属性:会被外部修改的属性
  • 内部属性:实例创建之后就不会变更的属性

从一个 MP3 案例谈起

现编案例,如有不恰当可以指正

案例
假设我们现在有一个系统用于管理每个人的 MP3 设备,这些 MP3 有不同的颜色、牌子、型号…还有每个人在设备里的个性化配置、自己导入的音乐。

代码实现
系统的早期实现版本如下:

import tracemalloc
from collections import namedtuple

def parse_comma_data(raw_data:str, char=","):
    lines = raw_data.split("\n")
    return (l.split(char) for l in lines)

class MP3:
    def __init__(self, brand, model, memory_size, color, settings, music_list=None):
        self.brand = brand
        self.model = model
        self.memory_size = memory_size
        self.color = color
        self.settings = settings
        self.music_list = music_list
        self.equipment_parameters = bytes(10000000)
        self.system = bytes(10000000)
        
    def __str__(self):
        return f"{self.model} {self.color} {self.memory_size} with id:{id(self)}"

    def __repr__(self):
        return self.__str__()
    
    def add_music(self, new_music:str):
        if self.music_list is None:
            self.music_list = []
            
        self.music_list.append(new_music)
    
# 系统需要管理的 MP3 设备
mp3_raw_list = """john,sony,S1001,4G,red
cindy,sony,S1001,4G,red
simon,sony,S1001,4G,red
lucy,sony,S1001,4G,red
babala,sony,S1002,4G,red
tom,sony,S1002,4G,red
dikaer,sony,S1005,16G,red"""

def main():
    mp3_devices = {}
    for data in parse_comma_data(mp3_raw_list):
        name,brand,model,memory_size,color = data
        mp3_devices[name] = MP3(
            brand, model, memory_size, color, settings=name
        )
    
    return mp3_devices
    
if __name__ == "__main__":
    tracemalloc.start()
    mp3_list = main()
    print("内存占用:", tracemalloc.get_traced_memory()[0])
    tracemalloc.stop()
    for name, mp3 in mp3_list.items():
        print(name, mp3)

上面这段代码将mp3_raw_list 中记录的每个独立的 MP3 导入系统中管理,系统可以根据指定用户调用他们对应的 mp3 对象 (mp3_list[name])。

值得注意的是 MP3 class 中有两个属性比较大,比较占内存空间:

        self.equipment_parameters = bytes(10000000)
        self.system = bytes(10000000)

同时,我们调用了 tracemalloc 库来监测内存的使用情况,并在代码最后打印每个设备的 id, 下面是它的控制台输出

内存占用: 140004589
john S1001 red 4G with id:1658049569424
cindy S1001 red 4G with id:1658049567568
simon S1001 red 4G with id:1658049573200
lucy S1001 red 4G with id:1658049567504
babala S1002 red 4G with id:1658049561936
tom S1002 red 4G with id:1658049561808
dikaer S1005 red 16G with id:1658049562576

从这段输出我们可以看到三个点:

  1. 这些 mp3 设备有一些是同一款产品,比如 john、cindy、simon、lucy 他们四个人都是 sony,S1001,4G,red, 但也注意到他们每个人的设置都不一样,这点可以看 mp3.settings 这个属性都不同就知道
  2. 这些 mp3 对象在系统中都是独立的,这点可以从它们的 id 都不同看出来
  3. 每个 mp3 对象的内存开销大部分消耗在设备基础属性(equipment_parameters)和操作系统(system), 每增加一个在线用户就需要增加 20000000 字节的空间消耗:
     # self.equipment_parameters = bytes(10000000)
     # self.system = bytes(10000000)
    上面有 7 个设备,则消耗:
    10000000 * 2 * 7 = 140000000 
    这个数值接近上面的控制台输出 140004589
    

flyweight 模式解决

flyweight pattern 的组件

首先我们来看下 flyweight 模式有哪些关键组件,然后再按些组件的功能定义来实现代码。

  • Flyweight : 包含多个对象共享的固有状态,同一个 flyweight 对象可在不同的上下文中使用。它存储内在状态,并从上下文中接收外在状态。
  • Context: 保存所有原始对象独有的外在状态。当与一个 flyweight 对象配对时,它代表了原始对象的全部状态。
  • Flyweight Factory: 管理现有的 flyweight 实例池,处理 flyweight 实例的创建和重用。Client 通过与 factory 交互来获取 flyweight 实例,并传递 内部属性 以进行检索或创建。
  • Client: 计算或存储 flyweight 对象的外部属性。 它将 flyweight 视为模板对象,并在运行时通过向其方法中传递上下文数据对其进行配置。

拆解定义与逐步实现

这段定义显然不够清晰,那我们配合着图形与案例看它们的关系:

对于我们的 MP3 实例而言,它应该是由内在状态(属性)和外在状态(属性)组成

MP3 对象属性
intrinsic 内部属性
extrinsic 外部属性
model,brand,color,memory_size
settings, music_list

显然除了self.settingsself.music_list 两个属性是用户在拥有设备后可以更改的属性,其他的属性无法修改(这里指的是现实商品层面的无法修改,我们并没有在代码实现上阻止调用者修改这些实例属性)

根据上面的定义知道两点:

  1. flyweight 对象具有 MP3 实例的内部属性
  2. context 对象具有 MP3 实例的内部属性外部属性

根据这两点,我们继续演进上面的关系图:

MP3 obj
intrinsic 内部属性
extrinsic 外部属性
flyweight obj
context obj

从这里我们可以看到 MP3 objcontext obj 是指代一种东西,即一个完整的 MP3 对象,实现的过程中命名为 context 还是 MP3 都是一样的,区别在于

mp3_obj = 内部属性 + 外部属性
context_obj = flyweight_obj + 外部属性

现在我们可以完成代码中 flyweightcontext 部分的实现了:

# 此时 MP3 是一个 flyweight 对象
class MP3:
    def __init__(self, brand, model, memory_size, color):
        self.brand = brand
        self.model = model
        self.memory_size = memory_size
        self.color = color
        
        # 比较占内存空间的变量
        self.equipment_parameters = bytes(1000000)
        self.system = bytes(1000000)

        
    def __str__(self):
        return f"{self.model} {self.color} {self.memory_size} with id:{id(self)}"

    def __repr__(self):
        return self.__str__()
        

# context 对象,或者说具体的 mp3 对象,它具备了其内部属性和外部属性
class PersonalMP3:
    def __init__(self, brand,model,memory_size,color, settings="default setting", music_list=None):
        self.mp3 = MP3(brand,model,memory_size,color)
        self.settings = settings
        self.music_list = music_list
        
    def add_music(self, new_music:str):
        if self.music_list is None:
            self.music_list = []
            
        self.music_list.append(new_music)

上面这段代码我们让 MP3 class 只保留内部属性;而 PersonalMP3 class 则由这个 MP3 class 和外部属性组成。

需要注意的是,此时我们的 PersonalMP3.mp3 并没有复用,它根据入参实例化一个对象!

现在回过头来看 flyweight factory 的定义:

  1. 管理现有的 flyweight 实例池
  2. 传递 内部属性 以进行检索或创建

基于这段定义,我们需要继续修改上面的关系图:
在这里插入图片描述
根据第二点(传递 内部属性 以进行检索或创建),flyweight factory 返回实例的逻辑图应该是:
在这里插入图片描述
再次修改代码,增加 flyweight factory , 并修改上面 PersonalMP3 属性中 MP3 实例的获取逻辑。

from collections import namedtuple

Device = namedtuple("Device", ["brand", "model", "memory_size", "color"])

class MP3:
	# 省略
	...

# flyweight factory 用于提供 flyweight obj
class MP3Factory:
    cache = {}
    
    @classmethod
    def get_mp3(cls, device: Device):
        if device not in cls.cache:
            cls.cache[device] = MP3(
                brand=device.brand,
                model=device.model,
                memory_size=device.memory_size,
                color=device.color
            )
        
        return cls.cache[device]


# context 对象,或者说具体的 mp3 对象,它具备了其内部属性和外部属性
class PersonalMP3:
    def __init__(self, device:Device, settings="default setting", music_list=None):
        self.mp3 = MP3Factory.get_mp3(device)
        # 省略
		...

这段代码中我们在 MP3Factory 中维护了一个字典,用内部属性(Device 具名元组 )作为键索引其对应的 flyweight 实例。

  1. 若不存在则新建后放进字典里并返回
  2. 若存在则直接返回

完整代码

现在我们将所有代码合并在一起:

from collections import namedtuple
import tracemalloc

Device = namedtuple("Device", ["brand", "model", "memory_size", "color"])

def parse_comma_data(raw_data:str, char=","):
    lines = raw_data.split("\n")
    return (l.split(char) for l in lines)

# 此时 MP3 是一个 flyweight 对象
class MP3:
    def __init__(self, brand, model, memory_size, color):
        self.brand = brand
        self.model = model
        self.memory_size = memory_size
        self.color = color
        
        # 比较占内存空间的变量
        self.equipment_parameters = bytes(1000000)
        self.system = bytes(1000000)

        
    def __str__(self):
        return f"{self.model} {self.color} {self.memory_size} with id:{id(self)}"

    def __repr__(self):
        return self.__str__()
    


# flyweight factory 用于提供 flyweight obj
class MP3Factory:
    cache = {}
    
    @classmethod
    def get_mp3(cls, device: Device):
        if device not in cls.cache:
            cls.cache[device] = MP3(
                brand=device.brand,
                model=device.model,
                memory_size=device.memory_size,
                color=device.color
            )
        
        return cls.cache[device]


# context 对象,或者说具体的 mp3 对象,它具备了其内部属性和外部属性
class PersonalMP3:
    def __init__(self, device:Device, settings="default setting", music_list=None):
        self.mp3 = MP3Factory.get_mp3(device)
        self.settings = settings
        self.music_list = music_list
        
    def add_music(self, new_music:str):
        if self.music_list is None:
            self.music_list = []
            
        self.music_list.append(new_music)
        


mp3_list = """john,sony,S1001,4G,red
cindy,sony,S1001,4G,red
simon,sony,S1001,4G,red
lucy,sony,S1001,4G,red
babala,sony,S1002,4G,red
tom,sony,S1002,4G,red
dikaer,sony,S1005,16G,red"""


def main():
    mp3_devices = {}
    for data in parse_comma_data(mp3_list):
        name, *device = data
        mp3_devices[name] = PersonalMP3(Device(*device), settings=name)
    
    return mp3_devices
    
tracemalloc.start()
mp3_list = main()
print("内存占用:", tracemalloc.get_traced_memory())
tracemalloc.stop()

for name, mp3 in mp3_list.items():
    print(name, mp3.mp3)

控制台输出:

内存占用: 6004821
john S1001 red 4G with id:1658046368208
cindy S1001 red 4G with id:1658046368208
simon S1001 red 4G with id:1658046368208
lucy S1001 red 4G with id:1658046368208
babala S1002 red 4G with id:1658046370512
tom S1002 red 4G with id:1658046370512
dikaer S1005 red 16G with id:1658046378256

注:这里打印出来的 id 不是 PersonalMP3 的 id, 而是 PersonalMP3.mp3 的 id

相比上一个版本的实现,内存的开销缩小了 23 倍(140004589/6004821),尽管每个 PersonalMP3id 还是独立的,但是每个实例下面的 mp3 属性指向的实例对象的 id 则存在相同的现象,即有些是共用的,比如上面的 john、cindy、simon、lucy

读到这里,你或许会问组件定义中的 client 怎么没提及,这段实际上是代码中的 main 函数。

未讨论问题

  1. 上面的 add_music 这里对象本身的行为是否一定要从 flyweight 对象中拆分出来?
  2. 这个模式引入什么弊病?

先吃饭,下次再补全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2042371.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国产崛起,Solon:我们的性能是 Spring 的 300%

Solon 应用开发框架(java framework)。是从零开始构建,有自主的标准规范与开放生态。纯血国产。 追求: 更快、更小、更简单提倡: 克制、简洁、高效、开放、生态 相对于 Spring 应用开发框架。并发高 300%&#xff1b…

el-cascader多选的父子关联和父子不关联功能

公用html&#xff1a; <el-cascader v-model"data" :options"optionsData" :props"props" clearable> </el-cascader> 公用js变量&#xff1a; data () {return {// 绑定的数组data: [],// 绑定的选项数据optionsData: []} }, 公…

python自动化笔记:操作mysql数据库

操作mysql数据库常见方法 1、第三方库&#xff1a;pymysql1.1、安装pymysql1.2、连接数据库1.3、连接指定数据库1.4 创建数据库、创建表1.5、表中插入数据1.6、批量插入数据1.7、获取查询结果数据1.8、防sql注入&#xff0c;sql语句中一般用占位符传值 2、标准库 &#xff1a;m…

OpenCV图像处理——按最小外接矩形剪切图像

引言 在图像处理过程中&#xff0c;提取感兴趣区域&#xff08;ROI&#xff09;并在其上进行处理后&#xff0c;往往需要将处理后的结果映射回原图像。这一步通常涉及以下几个步骤&#xff1a; 找到最小外接矩形&#xff1a;使用 cv::boundingRect 或 cv::minAreaRect 提取感兴…

Jenkins默认工作目录修改及迁移(Windows)

问题描述 如果Jenkins安装时安在C盘&#xff0c;那么默认的空间就是C:\ProgramData\Jenkins.jenkins&#xff0c;git下拉的项目会在该目录的workspace下&#xff0c;造成C盘空间增大。网上方法倒是有&#xff0c;五花八门&#xff0c;我这里是Windows&#xff0c;是有更简单的…

前端工程化14-git merge 以及 git rebase。

rebase会把当前分支的 commit 放到公共分支的最后面,所以叫变基。就好像从公共分支又重新拉出来这个分支一样。 举例&#xff1a; 如果从 master 拉个feature分支出来,然后提交了几个 commit,这个时候刚好有人把他开发的东西合并到 master 了,这个时候 master 就比你拉分支的…

VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection

VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection Abstract 摘要部分&#xff0c;作者首先指出了3D点云中目标检测的重要性&#xff0c;在自动驾驶导航、家政机器人以及增强现实和虚拟现实等多个领域有重要的作用。然后&#xff0c;提到了现有方法的…

C语言实现多种快速排序

目录 1.概念 2.快速排序hoare版本 2.1基本思想 2.2解释相遇处的值为何一定小于key 2.3hoare版本快速排序的实现 3.快速排序挖坑法 3.1基本思想 3.2挖坑法快速排序的实现 4. 快速排序前后指针版本 4.1基本思想 4.2快速排序前后指针版本实现 5.快速排序非递归版本 …

Linux下如何使用Curl进行网络请求

在Linux系统上&#xff0c;Curl是一个非常强大的网络请求工具&#xff0c;可以用于发送各种类型的HTTP请求&#xff0c;并获取响应结果。它支持常见的HTTP方法&#xff0c;如GET、POST、PUT、DELETE等&#xff0c;还支持HTTPS、FTP等不同协议。Curl提供了丰富的参数选项&#x…

多智能体新进展||斯坦福大学提出新模型‘Hypothetical Minds‘,让AI更懂人类思维

AI论文解读轻松掌握AI前沿技术进展&#xff0c;实时追踪AI动态&#xff0c;互动交流&#xff0c;共同成长进步 标题&#xff1a;Hypothetical Minds: Scaffolding Theory of Mind for Multi-Agent Tasks with Large Language Models 作者&#xff1a;Logan Cross, Violet Xia…

[数据集][图像分类]超声波肾脏结石分类数据集9416张2类别

数据集类型&#xff1a;图像分类用&#xff0c;不可用于目标检测无标注文件 数据集格式&#xff1a;仅仅包含jpg图片&#xff0c;每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数)&#xff1a;9416 分类类别数&#xff1a;2 类别名称:["normal","stone&…

链表(哈希表,有序表)环形链表确定节点的方式

UnOrderedMap UnSortedMap --> C 哈希表&#xff08;无序组织&#xff09; 哈希表如果只有key 没有 value 是HashSet 哈希表如果有key 有 value 是HashMap 哈希表在使用的过程中所有的增删改查都是常数时间&#xff08;比较大&#xff09; 如果存放的是基础类型&#xf…

【网络】套接字(socket)编程——TCP版

接着上一篇文章&#xff1a;http://t.csdnimg.cn/GZDlI 在上一篇文章中&#xff0c;我们实现的是UDP协议的&#xff0c;今天我们就要来实现一下TCP版本的 接下来接下来实现一批基于 TCP 协议的网络程序&#xff0c;本节只介绍基于IPv4的socket网络编程 基于 TCP 的网络编程开…

Java基础入门15:算法、正则表达式、异常

算法&#xff08;选择排序、冒泡排序、二分查找&#xff09; 选择排序 每轮选择当前位置&#xff0c;开始找出后面的较小值与该位置交换。 选择排序的关键&#xff1a; 确定总共需要选择几轮&#xff1a;数组的长度-1。 控制每轮从以前位置为基准&#xff0c;与后面元素选择…

一招搞定异构联邦学习难题:FedKTL的超高效策略!

【联邦学习】在近年来的深度学习领域中备受关注&#xff0c;它通过在保证数据隐私的前提下&#xff0c;协同多个分散的设备或服务器进行模型训练。联邦学习技术能够在不集中数据的情况下&#xff0c;实现数据共享和模型优化&#xff0c;在医疗、金融和智能设备等领域取得了显著…

Linux|centos7|奇怪的知识|perf命令,系统运行瓶颈分析工具

前言&#xff1a; Linux perf 是 Linux 2.6 后内置于内核源码树中的性能剖析&#xff08;profiling&#xff09;工具,它基于事件采样&#xff0c;以性能事件为基础&#xff0c;针对 CPU 相关性能指标与操作系统相关性能指标进行性能剖析&#xff0c;可用于性能瓶颈查找与热点代…

http/sse/websocket 三大协议演化历史以及 sse协议下 node.js express 服务实现打字机案例 负载均衡下的广播实现机制

背景 自从2022年底chatgpt上线后&#xff0c;sse就进入了大众的视野&#xff0c;之前是谁知道这玩意是什么&#xff1f;但是打字机的效果看起来是真的很不错&#xff0c;一度吸引了很多人的趋之若鹜&#xff0c;当然了这个东西的确挺好用&#xff0c;而且实现很简单&#xff0…

Linux环境本地搭建开发工具箱It-Tools并实现公网环境远程使用

文章目录 前言1. 安装Docker2.本地安装部署it-tools3. it-tools工具箱功能—生成docker-compose文件4. 安装cpolar内网穿透5. 固定it-tools公网地址 前言 本篇文章&#xff0c;我们将以Docker方式将IT-Tools部署至本地Linux系统个人服务器&#xff0c;并且结合cpolar内网穿透工…

【无标题】mysql读写分离架构+MyCAT实现读写分离

1、读写分离的目的 数据库负载均衡&#xff1a; 当数据库请求增多时&#xff0c;单例数据库不能够满足业务 需求。需要进行数据库实例的扩容。多台数据库同时相 应请求。也就是说需要对数据库的请求&#xff0c;进行负载均衡 但是由于数据库服务特殊原因&#xff0c;数据库…