享元模式

  • 定义:
    使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。

  • UML:


    image.png
    • Flyweight:抽象享元角色,为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。
    • ConcreteFlyweight:具体享元角色,实现抽象角色规定的方法。如果存在内蕴状态(享元对象可共有部分),就负责为内蕴状态提供存储空间。
    • FlyweightFactory:享元工厂角色,负责创建和管理享元角色。
    • Client: 客户端角色,维护对所有享元对象的引用,而且还需要存储对应的外蕴状态(享元对象不可共有部分)。
  • jdk中的享元模式:
    String对象是我们最常用的享元模式,Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,这个字符串常量池在jdk 6.0以前是位于常量池中,位于永久代,而在JDK 7.0中,JVM将其从永久代拿出来放置于堆中。

    public static void main(String[] args){
        String s = new String("abc");
        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        System.out.println(s==s1);
        System.out.println(s1==s2);
        System.out.println(s==s2);
        System.out.println(s==s3);
    }

执行结果:

false
true
false
false

通过new 对象的方式创建的String不是享元的,通过字面量创建的是享元的。除此之外jdk中的基本类型的自动拆装箱也使用了享元模式。

  • 设计思想:
    享元模式管理的细粒度对象有两个状态
    内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的
    外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。

  • 模型:景观设计图。
    景观设计图中,有大量的树与草这样的图片,在使用它们的时候,相同种类的树根草展示出来的效果图都是一样的,不同的是它们在设计图中的位置(x,y)坐标不同。而这些不同坐标的树或草其实使用的都是一张原型图,只要给定不同的坐标就能组合出我们的景观设计图。
    这里的原型图就是享元模式中抽象出来的Flyweight,也是内蕴状态,我们这里将树与草抽象出来植物类:

public abstract class Plant {
    public Plant() {}
        //不同的是展示的位置
    public abstract void display(int xCoord, int yCoord, int age);
}

具体享元草类:

public class Grass extends Plant {
    @Override
    public void display(int xCoord, int yCoord, int age) {
        // System.out.print("Grass x");
    }
}

具体享元树类:

public class Tree extends Plant {
    @Override
    public void display(int xCoord, int yCoord, int age) {
        // System.out.print("Tree x");
    }
}

植物工厂,根据植物的类型创建具体享元对象,这里我们只有2中类型

public class PlantFactory {
    private HashMap plantMap = new HashMap();
    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);
    }
}

享元对象的管理类,我们要创建一千万个植物,他们有不同的位置:

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]);
        }
    }
}

这里可以看出来,外蕴属性并不是享元对象维护的,而是由管理者维护。享元对象只需要维护自己的内蕴状态,我们这里的草,树没有设置内蕴属性。
我们将Client写成测试类,查看创建一千万个植物需要占用的内存:

public class MainTest {
    public static void main(String[] args) {
        showMemInfo();
        PlantManager mPlantManager;
        mPlantManager = new PlantManager();
        showMemInfo();
        mPlantManager.displayTrees();
        showMemInfo();
    }
    public static void showMemInfo() {
        // 最大内存:
        long max = Runtime.getRuntime().maxMemory() / 1024 / 1024;
        // 分配内存:
        long total = Runtime.getRuntime().totalMemory() / 1024 / 1024;
        // 已分配内存中的剩余空间 :
        long free = Runtime.getRuntime().freeMemory() / 1024 /1024;
        // 已占用的内存:
        long used = total - free;

        System.out.println("最大内存 = " + max + "M");
        System.out.println("已分配内存 = " + total + "M");
        System.out.println("已分配内存中的剩余空间 = " + free + "M");
        System.out.println("已用内存 = " + used + "M");
        System.out.println("时间 = " + System.currentTimeMillis());
        System.out.println("");
    }
}

输出:

最大内存 = 1806M
已分配内存 = 123M
已分配内存中的剩余空间 = 120M
已用内存 = 3M
时间 = 1517047082949

最大内存 = 1806M
已分配内存 = 200M
已分配内存中的剩余空间 = 44M
已用内存 = 156M
时间 = 1517047083852

最大内存 = 1806M
已分配内存 = 200M
已分配内存中的剩余空间 = 44M
已用内存 = 156M
时间 = 1517047083983

这里可以看到我们只使用到了77M内存。如果不使用享元模式,我们将树与草设计成自己维护坐标、年龄、类型。那将会极大的消耗内存,因为每个对象在堆中占用的空间至少大了12个字节,有兴趣可以尝试下。

你可能感兴趣的:(享元模式)