2019年5月14日22:13:58
java
享元模式(Flyweight),運用共享技術有效地支持大量細粒度的對象。——《設計模式:可複用面向對象軟件的基礎》設計模式
Flyweight在拳擊比賽中只最輕量級,即「蠅量即」和「雨量級」,這裏使用「享元模式」的意譯,是由於這樣更能反映模式的用意。——《JAVA與模式》數組
享元模式是對象結構型模式之一,此模式經過減小對象的數量,從而改善應用程序所需的對象結構。數據結構
咱們在須要建立大量(例如10^5)的類似的對象時,使用享元模式。反正我歷來沒有須要建立這麼多類似對象的時候,享元模式在真正的應用中用的要比較少,通常是一些底層數據結構使用到。好比,Java中的String。有圖:dom
圖中的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說明a
和b
是同一個對象,那a
和b
也共享了同一個char[]。this
第二個輸出:mingmingcome是a
改變前的字符串。設計
第三個輸出:true,是new出來的String實例對象,equals爲true,說明char[]相等。
在咱們後面利用反射修改c
中私有成員變量char[],a
、b
、c
、d
打印輸出都爲LOVEmingcome,充分說明a
、b
、c
、d
就是使用了享元模式共享了同一個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