享元模式(
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
从这段输出我们可以看到三个点:
- 这些 mp3 设备有一些是同一款产品,比如 john、cindy、simon、lucy 他们四个人都是
sony,S1001,4G,red
, 但也注意到他们每个人的设置都不一样,这点可以看mp3.settings
这个属性都不同就知道 - 这些 mp3 对象在系统中都是独立的,这点可以从它们的
id
都不同看出来 - 每个 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
视为模板对象,并在运行时通过向其方法中传递上下文数据对其进行配置。