享元模式:“享”就是分享之意,指一物被众人共享,而这也正是该模式的终旨所在。
享元模式有点类似于单例模式,都是只生成一个对象来被共享使用。这里有个问题,那就是对共享对象的修改,为了避免出现这种情况,我们将这些对象的公共部分,或者说是不变化的部分抽取出来形成一个对象。这个对象就可以避免到修改的问题。
享元的目的是为了减少不会要额内存消耗,将多个对同一对象的访问集中起来,不必为每个访问者创建一个单独的对象,以此来降低内存的消耗。
举个例子,例如我的世界中
有各种各样,有草地、沙漠、荒原,水路等等,在写代码之前,我们先思考下应该怎样去建模。
对于这种地图,我们加载一整张图片来做地图?如果地图太大,图片加载相当卡顿吧?而且大片地图上其实都是重复的图片素材,整图加载设计也有失灵活性。再仔细观察下,这地图无非就是很多小图片(元)拼起来的哦,这不就是类似于我们装修时贴马赛克嘛?
这可简单了!我们应该有个砖块类,持有“图片”,“位置”等属性信息,然后实例化这些砖块再调用其“绘制”方法把图片显示在地图某位置上即可。二话不说开始写代码。
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 16:16
* @Version 1.0
image
x y
image x y
image image
out image
x x
y y
out x y image
运行一下:
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 16:18
* @Version 1.0
args
从磁盘加载[河流]图片,耗时半秒。。。在位置[10:10]上绘制图片:[河流]
从磁盘加载[河流]图片,耗时半秒。。。在位置[10:20]上绘制图片:[河流]
从磁盘加载[石路]图片,耗时半秒。。。在位置[10:30]上绘制图片:[石路]
从磁盘加载[草坪]图片,耗时半秒。。。在位置[10:40]上绘制图片:[草坪]
从磁盘加载[草坪]图片,耗时半秒。。。在位置[10:50]上绘制图片:[草坪]
从磁盘加载[草坪]图片,耗时半秒。。。在位置[10:60]上绘制图片:[草坪]
从磁盘加载[草坪]图片,耗时半秒。。。在位置[10:70]上绘制图片:[草坪]
从磁盘加载[草坪]图片,耗时半秒。。。在位置[10:80]上绘制图片:[草坪]
有没有发现问题?每加载一张图都要耗费掉半秒钟,才画了8张地砖图就4秒钟流逝了,如果构建整张地图得多少时间?这就像是在慢性自杀,如此效率严重影响了游戏的用户体验,光卡顿在地图加载这给漫长的过程就已经让玩家失去兴趣了。
相信大家一定想到了《设计模式是什么鬼(原型)》模式吧?对,我们把相同的图共享出来,用克隆的方式代替物件图实例化的过程,从而加快初始化速度。再想想,共享元貌似没什么问题,速度也加快了,但对象数量貌似还是个严重问题,每一个小物件图都要对应一个对象,这么个小游戏用得着那么大的内存开销么,搞不好甚至会造成内存溢出,嗯,设计模式一定还是有问题。
沿着共享的思路我们再看下到底需不需要这么多对象?这些对象不同的地方在于其坐标的不同,再就是材质的不同,也就是图的不同了,能不能从这些对象里抽取出来一些共同点呢?首先每个图的坐标都不一样,是没办法共享的,但是材质图是重复出现的,是可以共享的,同样的材质图会在不同的坐标位置上重复出现,那么这个材质图是可以做成共享元的。
既然坐标不能共享,那就不做为材质类的共享元属性,由客户端维护这些坐标并作为参数传入好了,而且这些材质都有绘制能力,那就先定义一个接口吧。
x y
当然,我们也可以用抽象类抽出更多的属性和方法代替接口,使子类变得简单,这里为了清晰说明问题就用接口。接下来是材质类们,统统实现这个绘制接口。
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 16:48
* @Version 1.0
image
image
out image
x y
out x y image
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 17:01
* @Version 1.0
image
image
out image
x y
out
out x y image
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 16:59
* @Version 1.0
image
image
out image
x y
out x y image
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 16:45
* @Version 1.0
image
image
out image
x y
out x y image
接下来就是实现“元之共享”的关键了,我们来做一个简单工厂类,看代码。
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 17:07
* @Version 1.0
images
images
image
imagesimage
image
imagesimage
imagesimage
imagesimage
imagesimage
这个图件工厂维护着所有元对象的图库,构造方法于会初始化一个哈希图的缓存”池“,当客户端于需要实例化图件的时候,我们先观察这个图库池里存在不存在已实例化过的图件,也就是看有无已做共享的图元,如果没有则实例化并加入图库共享池供下次使用,这便是”元之共享“的秘密了。巧夺天工的设计一气呵成,已经迫不及待去运行了。
* @Description TODO
* @Author yitianRen
* @Date 2019/9/24 17:22
* @Version 1.0
args
factory
factory
factory
factory
factory
factory
factory
factory
factory
从磁盘加载[河流]图片,耗时半秒。。。在位置[10:10]上绘制图片:[河流]
在位置[10:20]上绘制图片:[河流]
从磁盘加载[石路]图片,耗时半秒。。。在位置[10:30]上绘制图片:[石路]
从磁盘加载[草坪]图片,耗时半秒。。。在位置[10:40]上绘制图片:[草坪]
在位置[10:50]上绘制图片:[草坪]
在位置[10:60]上绘制图片:[草坪]
在位置[10:70]上绘制图片:[草坪]
在位置[10:80]上绘制图片:[草坪]
可以看到,我们抛弃了利用new关键字肆意妄为地制造对象,而是改用这个图件工厂去帮我们把元构建并共享起来。显而易见,我们看到运行结果中每次实例化对象会耗费半秒时间,再次请求对象时就不再会加载图片耗费时间了,也就是从共享图池直接拿到了,不再造次。更妙的是,如果画完整个地图只需要实例化需要用到的某些元素材而已,即使是那个大房子图件也只需要实例化一次就够了。至此,CPU速度,内存轻量化同时做到了优化,整个游戏用户体验得到了极大的提升。
享元的精髓当然重点不止于”享“,更重要的是对于元的辨识,例如那个从外部客户端传入的坐标参数,如果我们依然把坐标也当作共享对象元数据(内蕴状态)的话,那么这个结构将无元可享,大量的对象就如同世界上没有相同的两片树叶一样多不胜数,最终会导致图库池被撑爆,享元将变得毫无意义。所以,对于整个系统数据结构的分析、设计、规划显得尤为重要。
还没有评论,来说两句吧...