一、定義
享元模式(Flyweight Pattern)是池技術的重要實現方式,其定義如下:Use sharing to support large numbers of fine-grained objects efficiently.(使用共享對象可有效地支持大量的細粒度的對象。)
享元模式的定義為我們提出了兩個要求:細粒度的對象和共享對象。我們知道分配太多的對象到應用程序中將有損程序的性能,同時還容易造成內存溢出,那怎么避免呢?就是享元模式提到的共享技術。我們先來了解一下對象的內部狀態和外部狀態。
要求細粒度對象,那么不可避免地使得對象數量多且性質相近,那我們就將這些對象的信息分為兩個部分:內部狀態(intrinsic)與外部狀態(extrinsic)。
- 內部狀態
內部狀態是對象可共享出來的信息,存儲在享元對象內部并且不會隨環境改變而改變,它們可以作為一個對象的動態附加信息,不必直接儲存在具體某個對象中,屬于可以共享的部分。
- 外部狀態
外部狀態是對象得以依賴的一個標記,是隨環境改變而改變的、不可以共享的狀態。
有了對象的兩個狀態,我們就可以來看享元模式的通用類圖,如圖所示。
類圖也很簡單,我們先來看我們享元模式角色名稱。
- Flyweight——抽象享元角色
它簡單地說就是一個產品的抽象類,同時定義出對象的外部狀態和內部狀態的接口或實現。
- ConcreteFlyweight——具體享元角色
具體的一個產品類,實現抽象角色定義的業務。該角色中需要注意的是內部狀態處理應 該與環境無關,不應該出現一個操作改變了內部狀態,同時修改了外部狀態,這是絕對不允 許的。
- unsharedConcreteFlyweight——不可共享的享元角色
不存在外部狀態或者安全要求(如線程安全)不能夠使用共享技術的對象,該對象一般 不會出現在享元工廠中。
- FlyweightFactory——享元工廠
職責非常簡單,就是構造一個池容器,同時提供從池中獲得對象的方法。
享元模式的目的在于運用共享技術,使得一些細粒度的對象可以共享,我們的設計確實 也應該這樣,多使用細粒度的對象,便于重用或重構。我來看享元模式的通用代碼,先看抽 象享元角色。
//抽象享元角色
public abstract class Flyweight {
//內部狀態
private String intrinsic;
//外部狀態
protected final String Extrinsic;
//要求享元角色必須接受外部狀態
public Flyweight(String _Extrinsic){
this.Extrinsic = _Extrinsic;
}
//定義業務操作
public abstract void operate();
//內部狀態的getter/setter
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
抽象享元角色一般為抽象類,在實際項目中,一般是一個實現類,它是描述一類事物的 方法。在抽象角色中,一般需要把外部狀態和內部狀態(當然了,可以沒有內部狀態,只有 行為也是可以的)定義出來,避免子類的隨意擴展。
//具體享元角色
public class ConcreteFlyweight1 extends Flyweight{
//接受外部狀態
public ConcreteFlyweight1(String _Extrinsic){
super(_Extrinsic);
}
//根據外部狀態進行邏輯處理
public void operate(){
//業務邏輯
}
}
public class ConcreteFlyweight2 extends Flyweight{
//接受外部狀態
public ConcreteFlyweight2(String _Extrinsic){
super(_Extrinsic);
}
//根據外部狀態進行邏輯處理
public void operate(){
//業務邏輯
}
}
這很簡單,實現自己的業務邏輯,然后接收外部狀態,以便內部業務邏輯對外部狀態的依賴。注意,我們在抽象享元中對外部狀態加上了final關鍵字,防止意外產生,什么意外?獲得了一個外部狀態,然后無意修改了一下,池就混亂了!
在程序開發中,確認只需要一次賦值的屬性則設置為final類型,避免無意修改導 致邏輯混亂,特別是Session級的常量或變量。
//享元工廠
public class FlyweightFactory {
//定義一個池容器
private static HashMap<String,Flyweight> pool= new HashMap<String,Flyweight>();
//享元工廠
public static Flyweight getFlyweight(String Extrinsic){
//需要返回的對象
Flyweight flyweight = null;
//在池中沒有該對象
if(pool.containsKey(Extrinsic)){
flyweight = pool.get(Extrinsic);
} else {
//根據外部狀態創建享元對象
flyweight = new ConcreteFlyweight1(Extrinsic);
//放置到池中
pool.put(Extrinsic, flyweight);
}
return flyweight;
}
}
二、應用
2.1 優點和缺點
享元模式是一個非常簡單的模式,它可以大大減少應用程序創建的對象,降低程序內存 的占用,增強程序的性能,但它同時也提高了系統復雜性,需要分離出外部狀態和內部狀 態,而且外部狀態具有固化特性,不應該隨內部狀態改變而改變,否則導致系統的邏輯混 亂。
2.2 使用場景
在如下場景中則可以選擇使用享元模式。
- 系統中存在大量的相似對象。
- 細粒度的對象都具備較接近的外部狀態,而且內部狀態與環境無關,也就是說對象沒 有特定身份。
- 需要緩沖池的場景。