python 实现 享元模式
本文的目录地址
本文的代码地址
由于对象创建的开销,面向对象的系统可能会面临性能问题。性能问题通常在资源受限的嵌入式系统中出现,比如智能手机和平板电脑。大型复杂系统中也可能会出现同样的问题,因为要在其中创建大量对象(用户),这些对象需要同时并存。
这个问题之所以会发生,是因为当我们创建一个新对象时,需要分配额外的内存。虽然虚拟内存理论上为我们提供了无限制的内存空间,但现实却并非如此。如果一个系统耗尽了所有的物理内存,就会开始将内存页替换到二级存储设备,通常是硬盘驱动器(Hard Disk Drive,HDD)。在多数情况下,由于内存和硬盘之间的性能差异,这是不能接受的。固态硬盘(Solid State Drive,SSD)的性能一般比硬盘更好,但并非人人都使用SSD,SSD并不会很快全面替代硬盘。
除内存使用之外,计算性能也是一个考虑点。图形软件,包括计算机游戏,应该能够极快地渲染3D信息(例如,有成千上万颗树的森林或满是士兵的村庄)。如果一个3D地带的每个对象都是单独创建,未使用数据共享,那么性能将是无法接受的。
作为软件工程师,我们应该编写更好的软件来解决软件问题,而不是要求客户购买更多更好的硬件。享元设计模式通过为相似对象引入数据共享来最小化内存使用,提升性能。一个享元(Flyweight)就是一个包含状态独立的不可变(又称固有的)数据的共享对象。依赖状态的可变(又称非固有的)数据不应是享元的一部分,因为每个对象的这种信息都不同,无法共享。如果享元需要非固有的数据,应该由客户端代码显示地提供。
用一个例子可能有助于解释实际场景中如何使用享元模式。假设我们正在设计一个性能关键的游戏,例如第一人称射击(First-Person Shooter,FPS)游戏。在FPS游戏中,玩家(士兵)共享一些状态,如外在表现和行为。例如,在《反恐精英》游戏中,同一团队(反恐精英或恐怖分子)的所有士兵看起来都是一样的(外在表现)。同一个游戏中,(两个团队的)所有士兵都有一些共同的动作,比如,跳起、低头等(行为)。这意味着我们可以创建一个享元来包含所有共同的数据。当然,士兵也有许多因人而异的可变数据,这些数据不是享元的一部分,比如,枪支、健康状况和地理位置等。
现实生活中的例子
享元模式(Flyweight pattern)是一个用于优化的设计模式。因此,要找一个合适的现实生活的例子不太容易。我们可以把享元看做现实生活中的缓存区。例如,许多书店都有专用的书架来摆放最新和流行的出版物。这就是一个缓存区,你可以先在这些专用书架上看看有没有正在找的书籍,如果没找到,则可以让图书管理员来帮你。
软件的例子
Exaile音乐播放器使用享元来复用通过相同URL识别的对象(在这里是指音乐歌曲)。创建一个与已有对象的URL相同的新对象是没有意义的,所以复用相同的对象来节约资源。
Peppy是一个用Python语言实现的类XEmacs编辑器,它使用享元模式存储major mode状态栏的状态。这是因为除非用户修改,否则所有状态栏共享相同的属性。
应用案例
享元旨在优化性能和内存使用。所有嵌入式系统(手机、平板电脑、游戏终端和微控制器等)和性能关键的应用(游戏、3D图形处理和实时系统等)都能从其获益。
若想要享元模式有效,需要满足GoF的《设计模式》一书罗列的以下几个条件。
- 应用需要使用大量的对象
- 对象太多,存储/渲染它们的代价太大。一旦移除对象中的可变状态(因为在需要之时,应该由客户端代码显示地传递给享元),多组不同的对象可被相对更少的共享对象所替代。
- 对象ID对于应用不重要。对象共享会造成ID比较的失败,所以不能依赖对象ID(那些在客户端代码看来不同的对象,最终具有相同的ID)。
实现
由于之前已提到树的例子,那么就来看看如何实现它。在这个例子中,我们将构造一小片水果树的森林,小到能确保在单个终端页面中阅读整个输出。然而,无论你构造的森林有多大,内存分配都保持相同。下面这个Enum类型变量描述三种不同种类的水果树。
TreeType=Enum('TreeType','apple_tree cherry_tree peach_tree')
在深入代码之前,我们稍稍解释一下memoization与享元模式之间的区别。memoization是一种优化技术,使用一个缓存来避免重复计算那些在更早的执行步骤中已经计算好的结果。memoization并不是只能应用于某种特定的编程方式,比如面向对象编程(Object-Oriented Programming,OOP)。在Python中,memoization可以应用于方法和简单的函数。享元则是一种特定于面向对象编程优化的设计模式,关注的是共享对象数据。
在Python中,享元可以以多种方式实现。pool变量是一个对象池(换句话说,是我们的缓存)。注意:pool是一个类属性(类的所有实例共享的一个变量)。使用特殊方法__new__(这个方法在__init__之前被调用),我们把Tree类变换成一个元类,元类支持自引用。这意味着cls引用的是Tree类。当客户端要创建Tree的一个实例时,会以tree_type参数传递树的种类。树的种类用于检查是否创建过相同种类的树。如果是,则返回之前创建的对象;否则,将这个新的树种添加到池中,并返回相应的新对象,如下所示。
def __new__(cls, tree_type):obj=cls.pool.get(tree_type, None)if not obj:obj=object.__new__(cls)cls.pool[tree_type]=objobj.tree_type=tree_typereturn obj
方法render()用于在屏幕上渲染一棵树。注意,享元不知道的所有可变(外部的)信息都需要客户端代码显示地传递。在当前案例中,每棵树都用到一个随机的年龄和一个x,y形式的位置。为了让render()更加有用,有必要确保没有树会被渲染到另一颗之上。你可以考虑把这个作为练习。如果你想让渲染更加有趣,可以使用一个图形工具包,比如Tkinter或Pygame。
def render(self,age,x,y):print('render a tree of type {} and age {} at ({},{})'.format(self.tree_type,age,x,y))
main()函数展示了我们可以如何使用享元模式。一棵树的年龄是1-30年之间的一个随机数。坐标使用1-100之间的随机数。虽然渲染了18棵树,但仅分配了3颗树的内存。输出的最后一行证明当使用享元时,我们不能依赖对象的ID。函数id()会返回对象的内存地址。Python规范并没有要求id()返回对象的内存地址,只是要求id()为每个对象返回一个唯一性ID,不过CPython(Python的官方实现)正好使用对象的内存地址作为对象唯一性ID。在我们的例子中,即使两个对象看起来不相同,但是如果它们属于同一个享元家族(在这里,家族由tree_type定义),那么它们实际上有相同的ID。当然,不同ID的比较仍然可用于不同家族的对象,但这仅在客户端知道实现细节的情况下才可行(通常并非如此)。
def main():rnd=random.Random()age_min,age_max=1,30min_point,max_point=0,100tree_counter=0for _ in range(10):t1=Tree(TreeType.apple_tree)t1.render(rnd.randint(age_min,age_max),rnd.randint(min_point,max_point),rnd.randint(min_point, max_point))tree_counter+=1for _ in range(3):t2=Tree(TreeType.cherry_tree)t2.render(rnd.randint(age_min,age_max),rnd.randint(min_point,max_point),rnd.randint(min_point, max_point))tree_counter+=1for _ in range(5):t3=Tree(TreeType.peach_tree)t3.render(rnd.randint(age_min,age_max),rnd.randint(min_point,max_point),rnd.randint(min_point, max_point))tree_counter+=1print('trees rendered: {}'.format(tree_counter))print('trees actually created: {}'.format(len(Tree.pool)))t4=Tree(TreeType.cherry_tree)t5=Tree(TreeType.cherry_tree)t6 = Tree(TreeType.apple_tree)print('{} == {}? {}'.format(id(t4),id(t5),id(t4)==id(t5)))print('{} == {}? {}'.format(id(t6), id(t5), id(t6) == id(t5)))
完整的代码(文件flyweight.py)
执行上面的程序的结果:
render a tree of type TreeType.apple_tree and age 28 at (32,67)
render a tree of type TreeType.apple_tree and age 12 at (41,25)
render a tree of type TreeType.apple_tree and age 20 at (54,16)
render a tree of type TreeType.apple_tree and age 29 at (88,50)
render a tree of type TreeType.apple_tree and age 24 at (42,93)
render a tree of type TreeType.apple_tree and age 20 at (38,46)
render a tree of type TreeType.apple_tree and age 9 at (36,89)
render a tree of type TreeType.apple_tree and age 30 at (66,96)
render a tree of type TreeType.apple_tree and age 18 at (87,62)
render a tree of type TreeType.apple_tree and age 12 at (52,2)
render a tree of type TreeType.cherry_tree and age 8 at (49,43)
render a tree of type TreeType.cherry_tree and age 27 at (64,79)
render a tree of type TreeType.cherry_tree and age 15 at (50,31)
render a tree of type TreeType.peach_tree and age 20 at (15,80)
render a tree of type TreeType.peach_tree and age 1 at (60,74)
render a tree of type TreeType.peach_tree and age 6 at (21,31)
render a tree of type TreeType.peach_tree and age 5 at (10,12)
render a tree of type TreeType.peach_tree and age 4 at (53,54)
trees rendered: 18
trees actually created: 3
139957559065120 == 139957559065120? True
139957566714712 == 139957559065120? False
相关的设计模式
- Proxy模式
如果生成实例的处理需要花费较长时间,那么使用Flyweight模式可以提高程序的处理速度。
而Proxy模式则是通过设置代理提高程序的处理速度。
- Composite模式
有时可以使用Flyweight模式共享Composite模式中的Leaf角色。
python 实现 享元模式相关推荐
- Python设计模式-享元模式
Python设计模式-享元模式 基于Python3.5.2,代码如下 #coding:utf-8class Coffee:name = ""price = 0def __init_ ...
- Python设计模式之享元模式(8)
享元模式(Flyweight Pattern):复用现有的同类对象,改善资源使用 主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应 ...
- python设计模式(十):享元模式
元,可理解为python中的元类.最小粒度的类,系统中存在大量的相似对象时,可以选择享元模式提高资源利用率. # -*- coding:utf-8 -*- class FlyweightBase: d ...
- python 享元模式_python 设计模式之享元(Flyweight)模式
#写在前面 这个设计模式理解起来很容易.百度百科上说的有点绕口. #享元模式的定义 运用共享技术来有効地支持大量细粒度对象的复用. 它通过共享已经存在的对橡大幅度减少需要创建的对象数量.避免大量相似类 ...
- python设计模式之享元模式
一.享元模式定义: 享元模式是一种用于解决资源和性能压力时会使用到的设计模式,它的核心思想是通过引入数据共享来提升性能. 在开发3D游戏时,例如有成千上万的士兵或者有成千上万棵树,如果一个3D地带的每 ...
- python 享元模式_设计模式-创建型模式,python享元模式 、python单例模式(7)
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能.这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式. 享元模式尝 ...
- python 享元模式_Python与设计模式之享元模式
一.网上咖啡选购平台 假设有一个网上咖啡选购平台,客户可以在该平台上下订单订购咖啡,平台会根据用户位置进行线下配送.假设其咖啡对象构造如下: class Coffee: name = '' price ...
- python 享元模式_享元模式(python)
""" 享元模式 使用共享技术支持大量细粒度的对象 使用几种对象就可以表示所有的对象 应用场景:对象的结构基本相同,但需要生成大量的对象 ""&quo ...
- Python设计模式:享元模式和单例模式
设计模式八:享元模式和单例模式 什么是享元模式 享元模式是一个用于优化的设计模式,通过为相似对象引入数据共存来最小化内存使用,提升性能. 一个享元就是一个包含状态独立的不可变数据的对象,依赖状态的可变 ...
最新文章
- php播放ppt代码,PHP+JavaScript幻灯片代码
- word2003的页码设置
- ASP.NET没有魔法——ASP.NET MVC 与数据库之MySQLEF
- 对计算机领域中间层的理解
- oxyen eclipse 启动 报错 se启动提示javaw.exe in your current PATH、No java virtual machine
- 前端框架Bootstrap简单介绍
- python测试工具开发面试宝典3web抓取
- 服务器搜索文件命令,Centos文件搜索命令的讲解
- 开源经济模型 MAKRO
- html5创建网站的步骤,自己建网站的流程是什么,自己建网站需要什么步骤?
- 1.Btn、Div特效---利用border
- 玩游戏计算机配置,玩大型游戏需要什么配置
- 考研英语阅读理解总结
- android getruntime.exec 权限
- latex 二重积分、三重积分、闭合积分公式
- ros笔记 URDF仿真 3.joint
- python基础编程小实例12——比赛票数排序(初级)
- BetaFlight模块设计之十七:pinioBox任务分析
- 十大经典排序总结(C++/C 实现)
- Navicat 连接mysql 显示 Clinet dose not support authentication protocol request by server
热门文章
- 我屮艸芔茻!!!什么情况,就刚才,百年之功废于一旦!!博客园,你TM想吃翔了吧!...
- 第28章 LTDC—液晶显示中英文
- 面试经历---UC(2016年01月11日下午面试)
- 毒液:致命守护者HDTC1080清晰英语中字
- 决定未来的八大核心科技
- 小程序碰上浏览器搜索入口!
- Source Insight 代码自动排版
- yasm/nasm not found or too old. Use --disable-yasm for a crippled build.
- 浅谈 CRTP:奇异递归模板模式
- Artifact xxx:war exploded: Error during artifact deployment