应用程序的对象占用大量内存,使用享元模式(Flyweight Pattern)可以减少内存消耗。
问题提出
假设我们要构建一个类似 谷歌地图 的移动应用程序,我们要渲染显示各种兴趣点,例如咖啡馆、餐馆、医院、学校等。
兴趣点有坐标(x, y)、类型(咖啡馆、餐馆等)、图标等属性。
我们用代码尝试实现。
创建enum枚举PointType,列出相关类型。
创建类Point,它有属性x, y, type, icon分别表示X坐标、y坐标、类型以及图标,这些属性通过构造函数初始化,还有一个draw方法用于渲染。
再创建PointService类,用于获取相关兴趣点。实际的情况可能会用数据库存储各个兴趣点,获取兴趣点就是从数据库查询获取相关点,这里就简单创建一个Point列表,并添加一个Point到该列表,然后返回列表。
在Main中,创建一个PointService对象,然后遍历各个兴趣点并进行渲染。
我们部署该应用,运行一段时间发现崩溃了,究其原因,可能是由于它占用了太多的内存导致,因为很多移动设备是有内存限制的。
我们假设每个兴趣点Point的icon属性大小是20K,1000个兴趣点就是20M,表示刷新一屏需要占用20M的内存,这很可能会导致崩溃。
使用享元模式,则可以减少内存的占用。
解决方案
当前我们的结构是这样的。
这个结构会消耗大量内存,因为我们存储了很多类型图标。如果有1000个咖啡馆的兴趣点,我们就要存储1000个咖啡馆图标,显然这是不必要的,在内存中,咖啡馆图标实际只需存一份,使用它的兴趣点都通过链接来共享它。
我们将Point类的图标属性icon进行分离,由于icon依赖于类型type,同时将type也分离出去。我们引入PointIcon类来放置这两个属性。
Point对象持有PointIcon对象,它们是组合关系。
但如何确保咖啡馆图标只存储一份呢?
再引入PointIconFactory类。顾名思义,该工厂类的责任就是用于生产PointIcon对象,它有单一的getPointIcon方法。getPointIcon方法的实现逻辑是这样的:如果已存在某一类型的图标,就直接返回;如果不存在,则创建该类型图标并返回。
PointIconFactory与PointIcon的关系也是组合,前者可能有0个或多个后者。
最后,PointService使用PointIconFactory创建Point对象,这跟之前直接创建Point对象是不同的。
这个结构被称为Flyweight Pattern,中文通常翻译为享元模式或蝇量模式。
一个flyweight 就是我们可以共享的对象。这个例子里PointIcon就是Flyweight。
代码实现
将Point类的type和icon属性移出,放至新建的类PointIcon,并通过构造函数初始化,另外这两个属性在后面不能修改,添加final修饰。再添加getType用于获取类型。
修改Point类,添加PointIcon属性,并对draw方法作相应的修改。
我们要保证对每一种类型的图标只存一份,在Java中,可以使用Map或者HashMap。
创建PointIconFactory类,它持有键值对映射的icons,以及getPointIcon方法。在getPointIcon方法里,我们判断是否存在对应的类型图标,存在的话,直接返回类型图标,不存在的话就创建相应的类型图标并返回。
使用工厂类PointIconFactory可以确保同样的图标不会在内存中被创建多次。
最后到PointService,添加pointIconFactory属性,并通过它来获取类型图标PointIcon。
在Main类中进行测试,跟之前的结果是一样的。
使用了享元模板,我们能保证咖啡馆的图标只在内存中生成一份。
使用享元模式,我们将需要共享的数据分离到某个 Flyweight类,我们这个例子就是PointIcon类,然后使用一个工厂类缓存这些数据,这就是享元模式背后的想法。
小结
享元模式(Flyweight Pattern)可使我们减少大量对象对内存的消耗。