享元模式

2019年5月14日22:13:58java

享元模式(flyweight pattern)

定義

享元模式(Flyweight),運用共享技術有效地支持大量細粒度的對象。——《設計模式:可複用面向對象軟件的基礎》設計模式

Flyweight在拳擊比賽中只最輕量級,即「蠅量即」和「雨量級」,這裏使用「享元模式」的意譯,是由於這樣更能反映模式的用意。——《JAVA與模式》數組

享元模式是對象結構型模式之一,此模式經過減小對象的數量,從而改善應用程序所需的對象結構。數據結構

使用場景

咱們在須要建立大量(例如10^5)的類似的對象時,使用享元模式。反正我歷來沒有須要建立這麼多類似對象的時候,享元模式在真正的應用中用的要比較少,通常是一些底層數據結構使用到。好比,Java中的String。有圖:dom

IDEA String類實例

圖中的String類實例數就達到了20多萬,因此String使用了享元模式。再看看大小,char[]和String對比,差了一個數量級。按道理來講,char[]和String的大小應該是差很少的啊,爲何呢?咱們再看看源碼:編輯器

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    //省略
}

String中的char[]就是用來存儲字符串的,而後String只是保存着char[]的引用(即這個數組的內存地址)。因此char[]和String數量差很少,可是大小卻差了一個數量級的緣由是char[]存儲着真正的字符串內容,String只是存儲着char[]引用。並且這個char[]放在常量池中,經過享元模式被String引用,這樣子一個char[]就可能被多個String共享引用。多說無益,show me code。ide

public class StringDemo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String a = "mingmingcome";
        String b = "mingmingcome";

        System.out.println(a == b); // true
        System.out.println(a); // mingmingcome

        String c = new String("mingmingcome");
        String d = new String("mingmingcome");

        System.out.println(c.equals(d));

        // 利用反射修改String中私有成員變量char[]
        Field value = c.getClass().getDeclaredField("value");
        value.setAccessible(true);
        char[] o = (char[])value.get(c);
        o[0] = 'L';
        o[1] = 'O';
        o[2] = 'V';
        o[3] = 'E';

        System.out.println(a); //LOVEmingcome
        System.out.println(b); //LOVEmingcome
        System.out.println(c); //LOVEmingcome
        System.out.println(d); //LOVEmingcome

    }
}

上面的例子中控制檯打印爲:字體

true
mingmingcome
true
LOVEmingcome
LOVEmingcome
LOVEmingcome
LOVEmingcome

第一個輸出:true說明ab是同一個對象,那ab也共享了同一個char[]。this

第二個輸出:mingmingcome是a改變前的字符串。設計

第三個輸出:true,是new出來的String實例對象,equals爲true,說明char[]相等。

在咱們後面利用反射修改c中私有成員變量char[],abcd打印輸出都爲LOVEmingcome,充分說明abcd就是使用了享元模式共享了同一個char[]。

角色

享元工廠(Flyweight Factory):一個享元工廠,用來建立並管理Flyweight對象。它主要是用來確保合理地共享Flyweight,當用戶請求一個Flyweight是,FlyweightFactory對象提供一個已建立的實例或者建立一個(若是不存在的話)。J

抽象享元(Flyweight):全部具體享元的超類和接口,經過這個接口,Flyweight能夠接受並做用於外部狀態。

具體享元(Concrete Flyweight):繼承Flyweight超類或實現Flyweight接口,併爲內部狀態增長存儲空間。

非共享具體享元(Unshared Concrete Flyweight):指那些不須要共享的Flyweight子類。由於Flyweight接口共享成爲可能,但他並不強制共享。

內部狀態和外部狀態

內部狀態(intrinsic state):能夠用來共享的不變狀態,在具體享元模式中

外部狀態(extrinsic state):能夠做爲參數傳進來的可變狀態

爲了搞清楚內部狀態和外部狀態,咱們來看一個例子。

假設咱們在文本編輯器中輸入一個字符,一個Character對象被建立,Character類的屬性有名字、字體、大小{name,font,size}。咱們不須要當客戶端每次輸入一個字符時都建立一個對象,由於字符'B'和另一個字符'B'沒有什麼不同。若是客戶端再一次輸入'B',咱們只須要簡單地返回咱們以前建立的那個字符'B'對象便可。如今全部這些狀態{name,font,size}都是內部狀態,由於它們能夠在不一樣的對象中共享。

咱們Character類給添加更多的屬性:行和列,它們說明字符在文本中的位置。這些屬性對於相同的字符來講,也是不一樣的,由於沒有兩個character會在文本中有相同的位置。這些屬性被稱爲外部狀態,它們是不能在對象中共享的。

圖示

享元模式結構圖:

享元模式結構圖

代碼示例

實現:在反恐精英中恐怖分子和反恐精英的建立。咱們有兩個類,一個是恐怖分子Terrorist,另外一個是反恐精英Counter Terrorist。當玩家要求一個武器weapon,咱們分配給他一個武器。任務:恐怖分子負責放置炸彈,反恐精英負責拆除炸彈。

爲何在這個例子中使用享元模式?由於咱們須要減小玩家對象的數量,因此使用享元模式。若是咱們不使用享元模式,當有n個玩家玩CS,那麼咱們須要建立n個對象。如今咱們只須要建立兩個對象便可,一個是恐怖分子,一個是反恐精英,咱們能夠在須要的時候一次又一次地重用他們。

內部狀態:對於兩種類型的玩家來講,任務task是一個內部狀態,由於對於全部恐怖分子(或者反恐精英)來講,任務老是同樣的。

外部狀態:由於每一個玩家能夠攜帶任何他選擇的武器,因此武器weapon是一個外部狀態。武器做爲參數由客戶端傳遞。

類圖以下:

類圖

玩家接口:

public interface Player {
    void assignWeapon(String weapon);
}

恐怖分子:

public class Terrorist implements Player{
    private final String task;

    private String weapon;

    public Terrorist() {
        task = "放置炸彈";
    }

    @Override
    public void assignWeapon(String weapon) {
        this.weapon = weapon;
    }

    @Override
    public String toString() {
        return "恐怖分子{" +
                "task='" + task + '\'' +
                ", weapon='" + weapon + '\'' +
                '}';
    }
}

反恐精英:

public class CounterTerrorist implements Player{
    private final String task;

    private String weapon;

    public CounterTerrorist() {
        task = "放置炸彈";
    }

    @Override
    public void assignWeapon(String weapon) {
        this.weapon = weapon;
    }

    @Override
    public String toString() {
        return "反恐精英{" +
                "task='" + task + '\'' +
                ", weapon='" + weapon + '\'' +
                '}';
    }
}

玩家工廠:

public class PlayerFactory {
    private static HashMap<String, Player> hm = new HashMap<>();

    public static Player getPlayer(String playerType) {
        Player p = null;

        if (hm.containsKey(playerType)) {
            p = hm.get(playerType);
        } else {
            switch (playerType) {
                case "Terrorist":
                    p = new Terrorist();
                    System.out.println("恐怖分子已建立");
                    break;
                case "CounterTerrorist":
                    p = new CounterTerrorist();
                    System.out.println("反恐精英已建立");
                    break;
                default:
                    System.out.println("無此玩家類型");
            }
            hm.put(playerType, p);
        }
        return p;
    }
}

CS客戶端:

public class CounterStrike {
    private static String[] playerType = {"Terrorist", "CounterTerrorist"};

    private static String[] weapon = {"AK-47", "Maverick", "Gut Knife", "Desert Eagle"};

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Player p = PlayerFactory.getPlayer(getPlayerType());
            p.assignWeapon(getWeapon());
            System.out.println(p.toString());
        }
    }

    private static String getPlayerType() {
        Random r = new Random();
        int i = r.nextInt(playerType.length);
        return playerType[i];
    }

    private static String getWeapon() {
        Random r = new Random();
        int i = r.nextInt(weapon.length);
        return weapon[i];
    }
}

輸出:

恐怖分子已建立
恐怖分子{task='放置炸彈', weapon='Maverick'}
反恐精英已建立
反恐精英{task='放置炸彈', weapon='AK-47'}
恐怖分子{task='放置炸彈', weapon='AK-47'}
反恐精英{task='放置炸彈', weapon='Gut Knife'}
恐怖分子{task='放置炸彈', weapon='Desert Eagle'}
反恐精英{task='放置炸彈', weapon='Desert Eagle'}
恐怖分子{task='放置炸彈', weapon='Gut Knife'}
恐怖分子{task='放置炸彈', weapon='AK-47'}
反恐精英{task='放置炸彈', weapon='Gut Knife'}
反恐精英{task='放置炸彈', weapon='AK-47'}

優勢

  • 一、能夠極大地減小內存中對象的數量,使得相同對寧或類似對象在內存中只保存一份。
  • 二、外部狀態相對獨立,並且不會影響其內部狀態,從而使得享元對象能夠在不一樣的環境中被共享。

缺點

  • 一、使得系統更加複雜,須要分離出內部狀態和外部狀態。

總結

享元模式使用共享技術有效地支持大量細粒度的對象,減小內存中對象的數量。

享元模式有內部狀態和外部狀態,內部狀態能夠共享,外部狀態做爲參數傳入。

2019年5月19日22:13:48

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息