声明:转载请说明来源:http://www.cnblogs.com/pony1223/p/7554686.html
一、引出蝇量模式
现在假设有一个项目,这个项目是为公园设计一个景观的部署,那么这个时候就会有一个问题出现,那么就是避免不了的会有一个树的类,树会很多,那么这个时候如果按照传统的方案来合计,我们会这样的设计:
然后,我们会建立很多树的对象,属性的含义分别为:x坐标,y坐标,年轮,显示的样式(比如:小树苗,参天大树等)代码如下:
package study.designmode.flyweight; public class Tree { private int xCoord, yCoord, age; public Tree(int xCoord, int yCoord, int age) { this.xCoord = xCoord; this.yCoord = yCoord; this.age = age; } public void display() { // System.out.print("x"); } }
然后现在公园里面,现在需要很多树,那么这个时候,我们模拟如下:
package study.designmode.flyweight; public class TreesTest { private int length = 1000000; private Tree[] treelst = new Tree[length]; public TreesTest() { for (int i = 0; i < length; i++) { treelst[i] = new Tree((int) (Math.random() * length), (int) (Math.random() * length), (int) (Math.random() * length) % 5); } } public void display() { for (int i = 0, len = treelst.length; i < len; i++) { treelst[i].display(); } } }
现在就new 了一万颗树出来了,那么我测试下带来的内存消耗:
package study.designmode.flyweight; public class MainTest { public static void main(String[] args) { showMemInfo(); TreesTest mTreesTest; mTreesTest = new TreesTest(); showMemInfo(); mTreesTest.display(); showMemInfo(); } public static void showMemInfo() { // 最大内存: long max = Runtime.getRuntime().maxMemory(); // 分配内存: long total = Runtime.getRuntime().totalMemory(); // 已分配内存中的剩余空间 : long free = Runtime.getRuntime().freeMemory(); // 已占用的内存: long used = total - free; System.out.println("最大内存 = " + max); System.out.println("已分配内存 = " + total); System.out.println("已分配内存中的剩余空间 = " + free); System.out.println("已用内存 = " + used); System.out.println("时间 = " + System.currentTimeMillis()); System.out.println(""); } }
结果如下:
已分配内存 = 5177344
已分配内存中的剩余空间 = 4917312
已用内存 = 260032
时间 = 1505830341189
最大内存 = 66650112
已分配内存 = 45998080
已分配内存中的剩余空间 = 17842296
已用内存 = 28155784
时间 = 1505830341646
最大内存 = 66650112
已分配内存 = 45998080
已分配内存中的剩余空间 = 17842296
已用内存 = 28155784
时间 = 1505830341652
这样就带来一个比较严重的问题,对内存的消耗,即现在是1百万颗树,就new出来了这么多对象,如果树在多一点,或者说其他比如草,更多,带来的内存消耗就更大;那么如何来解决内存消耗的问题呢?
要想减小内存,必然要减少对象的出现,那就需要分析这些对象是否存在一些变与不变的东西,我们可以发现这些对象都很小,但是有一个共性就数量很大,那么针对对象小如苍蝇一样虽然小,但是量大,还是比较恐怖的,那么就引出了蝇量模式。
二、解决办法
首先,我们分析上述树这个类中,我们发现x坐标,y坐标,age 都是会变化的,而display 是随着x y age进行变化的,那就是说x y age 我们可以看成是一个外部状态是没有办法共享的,但是display 是可以共享,只是随外部状态变化而已,那这个display我们可以当做内部状态来进行处理;这样就可以分为两个对象,一个是持有display的蝇量对象,一个是控制 x y age的外部状态管理的管理对象。
蝇量模式:通过共享的方式高效的支持大量细粒度的对象。
代码实现如下:
1.蝇量对象:
package study.designmode.flyweight.ms; public class TreeFlyWeight { public TreeFlyWeight() { } public void display(int xCoord, int yCoord, int age) { // System.out.print("x"); } }
2.管理对象
package study.designmode.flyweight.ms; public class TreeManager { private int length = 1000000; int[] xArray = new int[length], yArray = new int[length], AgeArray = new int[length]; private TreeFlyWeight mTreeFlyWeight; public TreeManager() { mTreeFlyWeight = new TreeFlyWeight(); for (int i = 0; i < length; i++) { xArray[i] = (int) (Math.random() * length); yArray[i] = (int) (Math.random() * length); AgeArray[i] = (int) (Math.random() * length) % 5; } } public void displayTrees() { for (int i = 0; i < length; i++) { mTreeFlyWeight.display(xArray[i], yArray[i], AgeArray[i]); } } }
3.测试:
package study.designmode.flyweight.ms; public class MainTest { public static void main(String[] args) { showMemInfo(); TreeManager mTreeManager; mTreeManager = new TreeManager(); showMemInfo(); mTreeManager.displayTrees(); showMemInfo(); } public static void showMemInfo() { // 已分配内存中的剩余空间 : long free = Runtime.getRuntime().freeMemory(); // 分配内存: long total = Runtime.getRuntime().totalMemory(); // 最大内存: long max = Runtime.getRuntime().maxMemory(); // 已占用的内存: long used = total - free; System.out.println("最大内存 = " + max); System.out.println("已分配内存 = " + total); System.out.println("已分配内存中的剩余空间 = " + free); System.out.println("已用内存 = " + used); System.out.println("时间 = " + System.currentTimeMillis()); System.out.println(""); } }
结果如下:
最大内存 = 66650112
已分配内存 = 5177344
已分配内存中的剩余空间 = 4917312
已用内存 = 260032
时间 = 1505831079965
最大内存 = 66650112
已分配内存 = 14696448
已分配内存中的剩余空间 = 2527960
已用内存 = 12168488
时间 = 1505831080252
最大内存 = 66650112
已分配内存 = 14696448
已分配内存中的剩余空间 = 2527960
已用内存 = 12168488
时间 = 1505831080261
可以看出上面还是有点差异的,其实如果在更多对象的情况下,效果会更加明显,只是需要更多的内存来演示,否则容易内存溢出。
上面是蝇量模式的一个变异演示,为什么这么说呢?可以看下类图:
可见我们上面是直接一个蝇量对象,加管理对象完成的,如果要显示类上述样式,即要抽象出一个类和工厂来,为什么抽象,说明下:
1.我们采用蝇量模式的目的,就是为了解决对象小但数量多的问题,那么要解决,就要抽出内部状态和外部状态;那么任何一个都是这样来玩;比如上面的树,那么如果比如现在加入了草这个对象,也是一样的进行抽象;只是我在抽取蝇量对象的时候,发现树和草会有共性的出现,于是就抽化到了父类中,于是就出现了上面的抽象类,然后继承实现各个自己的蝇量。
2.既然抽象出了公共类,那么我们知道需要产生蝇量对象,如果对象种类比较多,这个时候我们可以采用工厂模式来做,并用一个集合来存放,已经有的蝇量对象就不在创建,如果没有就创建并放入到集合中使用;然后还有一个对象就是管理对象了client
代码如下:
package study.designmode.flyweight.fly; public abstract class Plant { public Plant() { } public abstract void display(int xCoord, int yCoord, int age); }
(1)树
package study.designmode.flyweight.fly; public class Tree extends Plant { @Override public void display(int xCoord, int yCoord, int age) { // TODO Auto-generated method stub // System.out.print("Tree x"); } }
(2)草
package study.designmode.flyweight.fly; public class Grass extends Plant { @Override public void display(int xCoord, int yCoord, int age) { // TODO Auto-generated method stub // System.out.print("Grass x"); } }
(3)工厂 先判断后获取
package study.designmode.flyweight.fly; import java.util.HashMap; public class PlantFactory { private HashMap<Integer, Plant> plantMap = new HashMap<Integer, Plant>(); public PlantFactory() { } public Plant getPlant(int type) { if (!plantMap.containsKey(type)) { switch (type) { case 0: plantMap.put(0, new Tree()); break; case 1: plantMap.put(1, new Grass()); break; } } return plantMap.get(type); } }
(4)管理对象
package study.designmode.flyweight.fly; public class PlantManager { private int length = 10000000; private int[] xArray = new int[length], yArray = new int[length], AgeArray = new int[length], typeArray = new int[length]; private PlantFactory mPlantFactory; public PlantManager() { mPlantFactory=new PlantFactory(); for (int i = 0; i < length; i++) { xArray[i] = (int) (Math.random() * length); yArray[i] = (int) (Math.random() * length); AgeArray[i] = (int) (Math.random() * length) % 5; typeArray[i]= (int) (Math.random() * length) % 2; } } public void displayTrees() { for (int i = 0; i < length; i++) { mPlantFactory.getPlant(typeArray[i]).display(xArray[i], yArray[i], AgeArray[i]); } } }
(5)测试:
package study.designmode.flyweight.fly; import java.util.ArrayList; public class MainTest { public static void main(String[] args) { showMemInfo(); PlantManager mPlantManager; mPlantManager = new PlantManager(); showMemInfo(); mPlantManager.displayTrees(); showMemInfo(); } public static void showMemInfo() { // 已分配内存中的剩余空间 : long free = Runtime.getRuntime().freeMemory(); // 分配内存: long total = Runtime.getRuntime().totalMemory(); // 最大内存: long max = Runtime.getRuntime().maxMemory(); // 已占用的内存: long used = total - free; System.out.println("最大内存 = " + max); System.out.println("已分配内存 = " + total); System.out.println("已分配内存中的剩余空间 = " + free); System.out.println("已用内存 = " + used); System.out.println("时间 = " + System.currentTimeMillis()); System.out.println(""); } }
结构图:
三、总结
Flyweight在拳击比赛中指最轻量级,即“蝇量级”或“雨量级”,这里选择使用“享元模式”的意译,是因为这样更能反映模式的用意。享元模式是对象的结构模式。享元模式以共享的方式高效地支持大量的细粒度对象。
Java中的String类型
在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String a="abc",其中"abc"就是一个字符串常量。
public class Test { public static void main(String[] args) { String a = "abc"; String b = "abc"; System.out.println(a==b); } }
上面的例子中结果为:true ,这就说明a和b两个引用都指向了常量池中的同一个字符串常量"abc"。这样的设计避免了在创建N多相同对象时所产生的不必要的大量的资源消耗。
享元模式的结构
享元模式采用一个共享来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。
一个内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。因此,一个享元可以具有内蕴状态并可以共享。
一个外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。
享元模式可以分成单纯享元模式和复合享元模式两种形式。
单纯享元模式
在单纯的享元模式中,所有的享元对象都是可以共享的。
单纯享元模式所涉及到的角色如下:
● 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
● 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
● 享元工厂(FlyweightFactory)角色 :本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
复合享元模式
在单纯享元模式中,所有的享元对象都是单纯享元对象,也就是说都是可以直接共享的。还有一种较为复杂的情况,将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
复合享元角色所涉及到的角色如下:
● 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
● 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
● 复合享元(ConcreteCompositeFlyweight)角色 :复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象。
● 享元工厂(FlyweightFactory)角色 :本角 色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有 一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个 合适的享元对象。
本质:内部状态和外部状态,解决大量细颗粒对象问题.