設計模式之享元模式(蠅量模式)
設計模式之享元模式(蠅量模式)
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
通過結果我們可以看出儘管添加了一個新的類,但是記憶體佔用和執行時間還是比初始的時候要小。
所以可以看出享元模式的好處。