在面向對象程序設計過程中,有時會面臨要創建大量相同或相似對象實例的問題。創建那麼多的對象將會耗費很多的系統資源,它是系統性能提高的一個瓶頸。
例如,圍棋和五子棋中的黑白棋子,圖像中的座標點或顏色,局域網中的路由器、交換機和集線器,教室裏的桌子和凳子等。
這些對象有很多相似的地方,如果能把它們相同的部分提取出來共享,則能節省大量的系統資源,這就是享元模式的產生背景。
定義與優缺點
享元(Flyweight)模式的定義:運用共享技術來有効地支持大量細粒度對象的複用。它通過共享已經存在的又橡來大幅度減少需要創建的對象數量、避免大量相似類的開銷,從而提高系統資源的利用率。
享元模式的主要優點是:相同對象只要保存一份,這降低了系統中對象的數量,從而降低了系統中細粒度對象給內存帶來的壓力。
其主要缺點是:
- 爲了使對象可以共享,需要將一些不能共享的狀態外部化,這將增加程序的複雜性。
- 讀取享元模式的外部狀態會使得運行時間稍微變長。
結構
基本介紹
- 享元模式提出了兩個要求:細粒度和共享對象。這裏就涉及到內部狀態和外部狀態了,即將對象的信息分爲兩個部分:內部狀態和外部狀態
- 內部狀態指對象共享出來的信息,存儲在享元對象內部且不會隨環境的改變而改變
- 外部狀態指對象得以依賴的一個標記,是隨環境改變而改變的、不可共享的狀態。
- 舉個例子:圍棋理論上有 361 個空位可以放棋子,每盤棋都有可能有兩三百個棋子對象產生,因爲內存空間有限,一臺服務器很難支持更多的玩家玩圍棋遊戲,如果用享元模式來處理棋子,那麼棋子對象就可以減少到只有兩個實例,這樣就很好的解決了對象的開銷問題,棋子顏色就是棋子的內部狀態,棋子座標就是棋子的外部狀態。
原理類圖
模式的角色及職責
- 抽象享元角色(Flyweight):是所有的具體享元類的基類,爲具體享元規範需要實現的公共接口,非享元的外部狀態以參數的形式通過方法傳入。
- 具體享元(Concrete Flyweight)角色:實現抽象享元角色中所規定的接口。
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部狀態,它以參數的形式注入具體享元的相關方法中。
- 享元工廠(Flyweight Factory)角色:負責創建和管理享元角色。當客戶對象請求一個享元對象時,享元工廠檢査系統中是否存在符合要求的享元對象,如果存在則提供給客戶;如果不存在的話,則創建一個新的享元對象。
模式的實現
//客戶端使用
public class Client {
public static void main(String[] args) {
FlyWeightFactory factory=new FlyWeightFactory();
FlyWeight f01=factory.getFlyWeight("a");
FlyWeight f02=factory.getFlyWeight("a");
FlyWeight f03=factory.getFlyWeight("a");
FlyWeight f11=factory.getFlyWeight("b");
FlyWeight f12=factory.getFlyWeight("b");
f01.operation(new UnSharedConcreteFlyWeight("第1次調用a。"));
f02.operation(new UnSharedConcreteFlyWeight("第2次調用a。"));
f03.operation(new UnSharedConcreteFlyWeight("第3次調用a。"));
f11.operation(new UnSharedConcreteFlyWeight("第1次調用b。"));
f12.operation(new UnSharedConcreteFlyWeight("第2次調用b。"));
}
}
//抽象享元角色
public interface FlyWeight {
public void operation(UnSharedConcreteFlyWeight state);
}
//具體享元角色
public class ConcreteFlyWeight implements FlyWeight {
private String key;
ConcreteFlyWeight(String key) {
this.key = key;
System.out.println("具體享元"+key+"被創建");
}
@Override
public void operation(UnSharedConcreteFlyWeight outState) {
System.out.print("具體享元"+key+"被調用,");
System.out.println("非享元信息是:"+outState.getInfo());
}
}
//非享元角色
public class UnSharedConcreteFlyWeight {
private String info;
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
UnSharedConcreteFlyWeight(String info) {
this.info = info;
}
}
//享元工廠角色
public class FlyWeightFactory {
private HashMap<String,FlyWeight> flyWeights = new HashMap<>();
public FlyWeight getFlyWeight(String key) {
FlyWeight flyWeight = flyWeights.get(key);
if (flyWeight != null) {
System.out.println("具體享元"+key+"已經存在,被成功獲取!");
} else {
flyWeight = new ConcreteFlyWeight(key);
flyWeights.put(key,flyWeight);
}
return flyWeight;
}
}
應用場景
- 統中存在大量相同或相似的對象,這些對象耗費大量的內存資源。
- 大部分的對象可以按照內部狀態進行分組,且可將不同部分外部化,這樣每一個組只需保存一個內部狀態。
- 由於享元模式需要額外維護一個保存享元的數據結構,所以應當在有足夠多的享元實例時才值得使用享元模式。
模式擴展
在前面介紹的享元模式中,其結構圖通常包含可以共享的部分和不可以共享的部分。在實際使用過程中,有時候會稍加改變,即存在兩種特殊的享元模式:單純享元模式和複合享元模式,下面分別對它們進行簡單介紹。
(1) 單純享元模式,這種享元模式中的所有的具體享元類都是可以共享的,不存在非共享的具體享元類,其結構圖如下圖所示。
(2) 複合享元模式,這種享元模式中的有些享元對象是由一些單純享元對象組合而成的,它們就是複合享元對象。雖然複合享元對象本身不能共享,但它們可以分解成單純享元對象再被共享,其結構圖如下圖所示。
享元模式在 JDK-Interger 的應用源碼分析
public class FlyWeight {
public static void main(String[] args) {
//如果 Integer.valueOf(x) x 在 -128 --- 127 直接,就是使用享元模式返回,如果不在範圍類,則仍然 new
//小結:
//1. 在 valueOf 方法中,先判斷值是否在 IntegerCache 中,如果不在,就創建新的 Integer(new), 否則,就直接從 緩存池返回
//2. valueOf 方法,就使用到享元模式
//3. 如果使用 valueOf 方法得到一個 Integer 實例,範圍在 -128 - 127 ,執行速度比 new 快
Integer x = Integer.valueOf(127); // 得到 x 實例,類型 Integer
Integer y = new Integer(127); // 得 到 y 實 例 , 類 型 Integer
Integer z = Integer.valueOf(127);//..
Integer w = new Integer(127);
System.out.println(x.equals(y)); // 大小,true
System.out.println(x == y ); // false
System.out.println(x == z ); // true
System.out.println(w == x ); // false
System.out.println(w == y ); // false
Integer x1 = Integer.valueOf(200); //不在IntegerCache中,創建新的 Integer(new)
Integer x2 = Integer.valueOf(200);//不在IntegerCache中,創建新的 Integer(new)
System.out.println("x1==x2" + (x1 == x2)); // false
}
}
參考文章:
享元模式(詳解版)