享元模式
定義:提供了減少對象數量從而改善應用所需的對象結構的方式,運用共享技術有效地支持大量細粒度的對象
類型:結構型
適用場景:
- 常常應用於系統底層的開發 ,以便解決系統的性能問題
- 系統有大量的相似對象、需要緩衝池的場景
優點:
- 減少對象的創建,降低內存中對象的數量,降低系統的內存,提高效率
- 減少內存之外的其他資源佔用
缺點:
- 需要關注內/外部狀態、關注線程安全問題
- 使系統、程序的邏輯負責化
享元模式擴展說明:
享元模式採用一個共享來避免大量擁有相同內容對象的開銷。這種開銷最常見、最直觀的就是內存的消耗。享元對象能做到共享的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。
一個內蘊狀態是存儲在享元對象內部的,並且是不會隨環境的改變而有所不同。因此,一個享元可以具有內蘊狀態並且可以共享。
一個外蘊狀態是隨環境的改變而改變的、不可以共享的。享元對象的外蘊狀態必須由客戶端保存,並在享元對象被創建之後,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,他們是相互獨立的。
享元模式可以分成單純享元模式和複合享元模式。
示例
單純享元模式
- 在單純的享元模式中,所有的享元對象都是可以共享的
抽象享元角色(Flyweight):給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。
具體享元角色(ConcreteFlyweight):實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。
享元工廠角色(FlyweightFactory):本角色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個合適的享元對象。
/**
* 抽象享元角色
*/
public interface Flyweight {
void operation(String state);
}
/**
* 具體享元角色
*/
public class ConcreteFlyweight implements Flyweight {
/**
* 內蘊狀態
*/
private String str;
public ConcreteFlyweight(String str) {
this.str = str;
}
/**
* state外蘊狀態作爲參數傳入方法中,改變方法的行爲,但是不改變內蘊狀態
* @param state
*/
@Override
public void operation(String state) {
System.out.println("內蘊狀態:"+str);
System.out.println("外蘊狀態:"+state);
}
}
/**
* 享元工廠角色
*/
public class FlyWeightFactory {
/**
* 維護一個對象緩存池,用於對象複用,必要時考慮併發共享問題
*/
private Map<String,ConcreteFlyweight> flyWeights = new HashMap<String, ConcreteFlyweight>();
public ConcreteFlyweight factory(String str)
{
ConcreteFlyweight flyweight = flyWeights.get(str);
if(null == flyweight){
flyweight = new ConcreteFlyweight(str);
flyWeights.put(str, flyweight);
}
return flyweight;
}
public int getFlyWeightSize()
{
return flyWeights.size();
}
}
/**
* 單純享元模式
*/
public class Client {
public static void main(String[] args) {
FlyWeightFactory factory = new FlyWeightFactory();
Flyweight f1 = factory.factory("a");
Flyweight f2 = factory.factory("b");
Flyweight f3 = factory.factory("a");
f1.operation("a fly weight");
f2.operation("b fly weight");
f3.operation("c fly weight");
System.out.println("f1和f2對象是否是同一個:" + (f1 == f3));
System.out.println("對象緩存池數量" + factory.getFlyWeightSize());
}
}
內蘊狀態:a
外蘊狀態:a fly weight
內蘊狀態:b
外蘊狀態:b fly weight
內蘊狀態:a
外蘊狀態:c fly weight
f1和f2對象是否是同一個:true
對象緩存池數量2
複合享元模式
- 在單純享元模式中,所有的享元對象都是單純享元對象,也就是說都是可以直接共享的。還有一種較爲複雜的情況,將一些單純享元使用合成模式加以複合,形成複合享元對象。這樣的複合享元對象本身不能共享,但是它們可以分解成單純享元對象,而後者則可以共享
抽象享元(Flyweight)角色: 給出一個抽象接口,以規定出所有具體享元角色需要實現的方法。
具體享元(ConcreteFlyweight)角色:實現抽象享元角色所規定出的接口。如果有內蘊狀態的話,必須負責爲內蘊狀態提供存儲空間。
複合享元(ConcreteCompositeFlyweight)角色 :複合享元角色所代表的對象是不可以共享的,但是一個複合享元對象可以分解成爲多個本身是單純享元對象的組合。複合享元角色又稱作不可共享的享元對象。
享元工廠(FlyweightFactory)角色 :本角 色負責創建和管理享元角色。本角色必須保證享元對象可以被系統適當地共享。當一個客戶端對象調用一個享元對象的時候,享元工廠角色會檢查系統中是否已經有 一個符合要求的享元對象。如果已經有了,享元工廠角色就應當提供這個已有的享元對象;如果系統中沒有一個適當的享元對象的話,享元工廠角色就應當創建一個 合適的享元對象。
複合享元對象是由單純享元對象通過複合而成的,因此提供了add()這樣的聚集管理方法。由於一個複合享元對象具有不同的聚集元素,這些聚集元素在複合享元對象被創建之後加入,這本身就意味着複合享元對象的狀態是會改變的,因此複合享元對象是不能共享的。
/**
* 具體的複合享元角色
*/
public class ConcreteCompositeFlyweight implements Flyweight {
private Map<String, Flyweight> flyWeights = new HashMap<String, Flyweight>();
/**
* 聚集管理方法,添加單純的享元角色對象
* @param key
* @param fly
*/
public void add(String key, Flyweight fly) {
flyWeights.put(key, fly);
}
@Override
public void operation(String state) {
Flyweight fly = null;
//操作複合享元對象時,裏面的單純享元對象的外蘊狀態都是同一個,都是state
for (String s : flyWeights.keySet()) {
fly = flyWeights.get(s);
fly.operation(state);
}
}
}
/**
* 享元工廠角色
*/
public class FlyweightCompositeFactory {
private Map<String, Flyweight> flyWeights = new HashMap<String, Flyweight>();
/**
* 複合享元對象工廠方法
* @param compositeStates
* @return
*/
public Flyweight factory(List<String> compositeStates) {
//創建複合享元對象
ConcreteCompositeFlyweight compositeFly = new ConcreteCompositeFlyweight();
for (String s : compositeStates) {
//往復合享元對象添加單純享元對象,this.factory(s)參數s是單純享元對象的內蘊狀態,每次添加的時候,內蘊狀態都不相等,取自於compositeStates集合
compositeFly.add(s, this.factory(s));
}
return compositeFly;
}
/**
* 單純享元對象工廠方法
* @param s
* @return
*/
public Flyweight factory(String s) {
Flyweight fly = flyWeights.get(s);
if (fly == null) {
fly = new ConcreteFlyweight(s);
flyWeights.put(s, fly);
}
return fly;
}
}
/**
* 複合享元模式
*/
public class Client {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("a");
list.add("b");
FlyweightCompositeFactory factory = new FlyweightCompositeFactory();
Flyweight f1 = factory.factory(list);
Flyweight f2 = factory.factory(list);
f1.operation("Composite Call");
System.out.println("=======");
System.out.println("複合享元模式是否可以共享對象:"+(f1 == f2));
String str = "a";
Flyweight f3 = factory.factory(str);
Flyweight f4 = factory.factory(str);
System.out.println("單純享元模式是否可以共享對象:"+(f3 == f4));
}
}
內蘊狀態:a
內蘊狀態:a
外蘊狀態:Composite Call
內蘊狀態:b
外蘊狀態:Composite Call
內蘊狀態:c
外蘊狀態:Composite Call複合享元模式是否可以共享對象:false
單純享元模式是否可以共享對象:true
從上述例子的運行結果可以看到,一個符合享元對象的所有單純享元對象元素的外蘊狀態都是與複合享元對象的外蘊狀態相等的。一個複合享元對象所含有的單純享元對象的內蘊狀態一般是不相等的。複合享元對象是不能共享的。單純享元對象是可以共享的。
相關的設計模式
- 享元模式和代理模式
如果生成的代理類需要耗費的資源和時間比較多,那麼就可以使用享元模式來提高生成對象的速度
- 享元模式和單例模式
容器單例就是享元模式和單例模式的結合,就是一種複用的思想
使用典範
- java.lang.Integer#valueOf(int)
- java.lang.Boolean#valueOf(boolean)
- java.lang.Byte#valueOf(byte)
- java.lang.String
- 數據庫的連接池