设计模式之享元模式(蝇量模式)
1. 什么是享元模式
Flyweight模式也叫享元模式,是构造型模式之一,它通过与其他类似对象共享数据来减小内存占用。
换句话说就是通过共享的方式高效地支持大量细粒度的对象。
享元模式的结构:
享元模式的角色和职责:
- 抽象享元角色:所有具体享元类的父类,规定一些需要实现的公共接口。
- 具体享元角色:抽象享元角色的具体实现类,并实现了抽象享元角色规定的方法。
- 享元工厂角色:负责创建和管理享元角色。
优缺点:
优点:
-
减少运行时的对象实例个数,节省创建开销和内存
-
将许多“虚拟”对象的状态集中管理
缺点:
-
系统设计更加复杂
-
需要专门维护对象的外部状态
适用场合:
-
需要大量细粒度对象
-
这些对象的外部状态不多
-
按照内部状态分成几个组,每一个组都仅用一个蝇量对象代替
2. 具体实例
我们有一个景观设计软件项目:
需要布置很多的树:XY坐标,树的大小,外观
假如需要10000000棵树,我们该怎么设计呢?
首先的想法就是抽象一个树的类,然后使用的时候初始化10000000个树的实例。
按照这种方式具体的实现一下:
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");
}
}
public class TreesTest {
private int length = 10000000;
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();
}
}
}
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("");
}
}
看一下运行的结果吧
最大内存 = 926941184
已分配内存 = 64487424
已分配内存中的剩余空间 = 62465304
已用内存 = 2022120
时间 = 1539587641214最大内存 = 926941184
已分配内存 = 361234432
已分配内存中的剩余空间 = 79750928
已用内存 = 281483504
时间 = 1539587644567最大内存 = 926941184
已分配内存 = 361234432
已分配内存中的剩余空间 = 79750928
已用内存 = 281483504
时间 = 1539587644583
我们这时候发现我们需要多少的树就要new出多少个树的对象,占用内存。
看一下树这个类,他的有些状态是共享的,如display这个方法,每一个树的显示都是一样的,然后像具体的坐标和年龄这个信息都相当于外部的状态,每一个都是不同的,我们可以设计一个新的类来管理这些信息,然后在实例化树的对象的时候就只需要实例化一个,然后每个树得信息从管理类中获取。那个管理的类中其实就是虚拟化了很多的树的类。这其实就是享元模式的设计思想,把相似的对象的数据进行共享。
看下具体的实现:
public class TreeFlyWeight {
public TreeFlyWeight() {
}
public void display(int xCoord, int yCoord, int age) {
// System.out.print("x");
}
}
public class TreeManager {
private int length = 10000000;
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]);
}
}
}
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("");
}
}
看一下运行结果:
最大内存 = 926941184
已分配内存 = 64487424
已分配内存中的剩余空间 = 62465304
已用内存 = 2022120
时间 = 1539588509988最大内存 = 926941184
已分配内存 = 145227776
已分配内存中的剩余空间 = 22870040
已用内存 = 122357736
时间 = 1539588510873最大内存 = 926941184
已分配内存 = 145227776
已分配内存中的剩余空间 = 22870040
已用内存 = 122357736
时间 = 1539588510881
通过比较发现内存被占用变小了,运行时间变小了。
其实享元模式就是通过共享细粒度对象的数据减少对象的数据的初始化或者是减少实例对象的创建。
再看一个更加复杂一点的例子。就是上面的景观项目,我不只要栽树,还要栽花。
这时候使用享元模式的设计的类图如下所示:
我们把草和树都抽象成为了一个plant的类,然后使用PlantManager来统一管理外部状态数据。但是有人说当我需要放置雕像的时候怎么办呢?那就直接把雕像单独作为一个类,然后抽象一个manager来做统一的管理。以后每添加一个对象要是可以抽象成为一组的就抽象,不能抽象成为一组的就直接单独成为一类。
看一下具体的代码实现:
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");
}
}
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");
}
}
public abstract class Plant {
public Plant() {
}
public abstract void display(int xCoord, int yCoord, int age);
}
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]);
}
}
}
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);
}
}
PlantFactory是用来管理和获取具体的plant类型的。
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("");
}
}
看一下测试类的结果:
最大内存 = 926941184
已分配内存 = 64487424
已分配内存中的剩余空间 = 62465304
已用内存 = 2022120
时间 = 1539589480028最大内存 = 926941184
已分配内存 = 185597952
已分配内存中的剩余空间 = 23240200
已用内存 = 162357752
时间 = 1539589481484最大内存 = 926941184
已分配内存 = 185597952
已分配内存中的剩余空间 = 23240200
已用内存 = 162357752
时间 = 1539589481658
通过结果我们可以看出尽管添加了一个新的类,但是内存占用和运行时间还是比初始的时候要小。
所以可以看出享元模式的好处。