享元模式(學習筆記)

  1. 意圖

  運用共享技術有效的支持大量細粒度的對象java

  2. 動機

   假設開發了一款簡單的遊戲:玩家們在地圖上移動並進行相互射擊。大量的子彈、導彈和爆炸彈片在整個地圖上穿行,爲玩家提供緊張刺激的遊戲體驗。可是,運行了幾分鐘後,遊戲由於內存容量不足而發生了崩潰。研究發現,每一個粒子(一顆子彈、 一枚導彈或一塊彈片)都由包含完整數據的獨立對象來表示。當玩家在遊戲中鏖戰進入高潮後的某一時刻,遊戲將沒法在剩餘內存中載入新建粒子,因而程序就崩潰了緩存

  

  仔細觀察粒子Particle類,會注意到顏色(color)和精靈圖(sprite)這兩個成員變量所消耗的內存要比其餘變量多得多。另外,對全部例子來講,這兩個成員變量所存儲的數據幾乎徹底同樣 (好比全部子彈的顏色和精靈圖都同樣)。每一個粒子的另外一些狀態 (座標 移動矢量和速度則是不一樣的,由於這些成員變量的數值會不斷變化。內在狀態存儲於flyweight中,它包含了獨立於場景的信息,這些信息使得flyweight能夠被共享。而外部狀態取決於flyweight場景,並根據場景而變化,所以不可共享。用戶對象負責在必要的時候將外部狀態傳遞給Flyweightdom

    

 

  3. 適用性

  • 程序須要生成巨大的類似對象,以致於消耗目標對象的全部內存
  • 對象中包含可抽取且能在多個對象間共享的重複狀態

  4. 結構

 

  5. 效果

  使用FlyWeight模式時,傳輸、查找和/或計算外部狀態都會產生運行時開銷,尤爲當flyweight原先被存儲爲內部狀態時。然而,空間上的節省抵消了這些開銷。共享的flyweight越多,空間節省越大ide

  存儲節約由如下因素決定:this

  1. 因爲共享帶來的實例總數減小的數量spa

  2. 對象內部狀態的平均數量rest

  3. 外部狀態是計算的仍是存儲的code

  共享的flyweight越多,存儲節約的也就越多。節約量隨着共享狀態的增多而增大。當對象使用大量的內部及外部狀態,而且外部狀態是計算出來的而非存儲的時候,節約量將達到最大對象

  6. 代碼實現  

  若是渲染一片森林 (1,000,000 棵樹)!每棵樹都由包含一些狀態的對象來表示 (座標和紋理等)。 儘管程序可以完成其主要工做,但很顯然它須要消耗大量內存。由於,太多樹對象包含重複數據 (名稱、 紋理和顏色)。所以咱們可用享元模式來將這些數值存儲在單獨的享元對象中 (Tree­Type類)。如今咱們再也不將相同數據存儲在數千個 Tree對象中,而是使用一組特殊的數值來引用其中一個享元對象。客戶端代碼不會知道任何事情, 由於重用享元對象的複雜機制隱藏在了享元工廠中blog

  trees/Tree.java: 包含每棵樹的獨特狀態

package flyweight.trees;

import java.awt.*;

/**
 * @author GaoMing
 * @date 2021/7/19 - 22:14
 */
public class Tree {
    private int x;
    private int y;
    private TreeType type;

    public Tree(int x, int y, TreeType type) {
        this.x = x;
        this.y = y;
        this.type = type;
    }

    public void draw(Graphics g) {
        type.draw(g, x, y);
    }
}

  trees/TreeType.java: 包含多棵樹共享的狀態

package flyweight.trees;

import java.awt.*;

/**
 * @author GaoMing
 * @date 2021/7/19 - 22:14
 */
public class TreeType {
    private String name;
    private Color color;
    private String otherTreeData;

    public TreeType(String name, Color color, String otherTreeData) {
        this.name = name;
        this.color = color;
        this.otherTreeData = otherTreeData;
    }

    public void draw(Graphics g, int x, int y) {
        g.setColor(Color.BLACK);
        g.fillRect(x - 1, y, 3, 5);
        g.setColor(color);
        g.fillOval(x - 5, y - 10, 10, 10);
    }
}

  trees/TreeFactory.java: 封裝建立享元的複雜機制

package flyweight.trees;

import java.awt.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author GaoMing
 * @date 2021/7/19 - 22:15
 */
public class TreeFactory {
    static Map<String, TreeType> treeTypes = new HashMap<>();

    public static TreeType getTreeType(String name, Color color, String otherTreeData) {
        TreeType result = treeTypes.get(name);
        if (result == null) {
            result = new TreeType(name, color, otherTreeData);
            treeTypes.put(name, result);
        }
        return result;
    }
}

  forest/Forest.java: 咱們繪製的森林

package flyweight.forest;

import flyweight.trees.TreeFactory;
import flyweight.trees.TreeType;
import flyweight.trees.Tree;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;

/**
 * @author GaoMing
 * @date 2021/7/19 - 22:16
 */
public class Forest extends JFrame {
    private List<Tree> trees = new ArrayList<>();

    public void plantTree(int x, int y, String name, Color color, String otherTreeData) {
        TreeType type = TreeFactory.getTreeType(name, color, otherTreeData);
        Tree tree = new Tree(x, y, type);
        trees.add(tree);
    }

    @Override
    public void paint(Graphics graphics) {
        for (Tree tree : trees) {
            tree.draw(graphics);
        }
    }
}

  Demo.java: 客戶端代碼

package flyweight;

import java.awt.*;
import flyweight.forest.Forest;

/**
 * @author GaoMing
 * @date 2021/7/19 - 22:17
 */
public class Demo {
    static int CANVAS_SIZE = 500;
    static int TREES_TO_DRAW = 1000000;
    static int TREE_TYPES = 2;

    public static void main(String[] args) {
        Forest forest = new Forest();
        for (int i = 0; i < Math.floor(TREES_TO_DRAW / TREE_TYPES); i++) {
            forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                    "Summer Oak", Color.GREEN, "Oak texture stub");
            forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                    "Autumn Oak", Color.ORANGE, "Autumn Oak texture stub");
        }
        forest.setSize(CANVAS_SIZE, CANVAS_SIZE);
        forest.setVisible(true);

        System.out.println(TREES_TO_DRAW + " trees drawn");
        System.out.println("---------------------");
        System.out.println("Memory usage:");
        System.out.println("Tree size (8 bytes) * " + TREES_TO_DRAW);
        System.out.println("+ TreeTypes size (~30 bytes) * " + TREE_TYPES + "");
        System.out.println("---------------------");
        System.out.println("Total: " + ((TREES_TO_DRAW * 8 + TREE_TYPES * 30) / 1024 / 1024) +
                "MB (instead of " + ((TREES_TO_DRAW * 38) / 1024 / 1024) + "MB)");
    }

    private static int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }
}

  運行結果

    

1000000 trees drawn
---------------------
Memory usage:
Tree size (8 bytes) * 1000000
+ TreeTypes size (~30 bytes) * 2
---------------------
Total: 7MB (instead of 36MB)

 

  7. 與其餘模式的關係

  • 可使用享元模式實現組合模式樹的共享葉節點以節省內存
  • 享元展現瞭如何生成大量的小型對象,外觀模式則展現瞭如何用一個對象來表明整個子系統
  • 若是你能將對象的全部共享狀態簡化爲一個享元對象,那麼享元就和單例模式相似了。但這兩個模式有兩個根本性的不一樣: 

  1. 只會有一個單例實體,可是享元類能夠有多個實體,各實體的內在狀態也能夠不一樣
  2. 單例對象能夠是可變的。享元對象是不可變的 

  8. 已知應用

  java.lang.Integer#valueOf(int) (以及 Boolean、Byte、Character、Short、Long 和 Big­Decimal)

  識別方法:享元能夠經過構建方法來識別,它會返回緩存對象而不是建立新的對象

相關文章
相關標籤/搜索