標籤:
這篇我們來介紹一下享元模式(Flyweight Pattern),Flyweight 代表輕量級的意思,享元模式是對象池的一種實現。享元模式用來儘可能減少記憶體使用量量,它適合用於可能存在大量重複對象的情境,緩衝可共用的對象,來達到對象共用和避免建立過多個物件的效果,這樣一來就可以提升效能,避免記憶體移除和頻繁 GC 等。
享元模式的一個經典使用案例是文本系統中圖形顯示所用的資料結構,一個文本系統能夠顯示的字元種類就是那麼幾十上百個,那麼就定義這麼些基礎字元對象,儲存每個字元的顯示外形和其他的格式化資料等,而不用每次都去建立對象,這樣就可以避免建立成千上萬的重複對象,大大提高對象的重用率。
轉載請註明出處:http://blog.csdn.net/self_study/article/details/51870660。
PS:對技術感興趣的同鞋加群544645972一起交流。
設計模式總目錄
java/android 設計模式學習筆記目錄
特點
使用共用對象可有效地支援大量細粒度的對象。
共用模式支援大量細粒度對象的複用,所以享元模式要求能夠共用的對象必須是細粒度對象。在瞭解享元模式之前我們先要瞭解兩個概念:內部狀態、外部狀態:
- 內部狀態:在享元對象內部不隨外界環境改變而改變的共用部分;
- 外部狀態:隨著環境的改變而改變,不能夠共用的狀態就是外部狀態。
由於享元模式區分了內部狀態和外部狀態,所以我們可以通過設定不同的外部狀態使得相同的對象可以具備一些不同的特性,而內部狀態設定為相同部分。在我們的程式設計過程中,我們可能會需要大量的細粒度對象,如果這些對象除了幾個參數不同外其他部分都相同,這個時候我們就可以利用享元模式來大大減少應用程式當中的對象。如何利用享元模式呢?這裡我們只需要將他們少部分的不同的狀態當做參數移動到類執行個體的外部去,然後在方法調用的時候將他們傳遞過來就可以了。這裡也就說明了一點:內部狀態儲存於享元對象內部,而外部狀態則應該由用戶端來考慮。
享元模式與對象池模式對比
看了上面的特徵會讓我們想到對象池模式,確實,對象池模式和享元模式有很多的相同點,但是他們有一個很重要的不同點:享元模式通常情況下擷取的是不可變的執行個體,而從對象池模式中擷取的對象通常情況下是可變的。所以使用享元模式用來避免建立多個擁有同樣狀態的對象,只建立一個並且在應用的不同地方都使用這一個執行個體;而對象池中的資源從使用的角度上看具有不同的狀態並且每個都需要單獨控制,但是又不想花費一定的資源區頻繁的建立和銷毀這些資來源物件,畢竟他們都有相同的初始化過程。
簡而言之,享元模式更加傾向於狀態的不可變性,而對象池模式則是狀態的可變性。
UML類圖
享元模式的 uml 類圖如下:
- Flyweight:
享元對象抽象基類或者介面;
- ConcreteFlyweight:
具體的享元對象;
- UnsharedConcreteFlyweight:
非共用具體享元類,指出那些不需要共用的Flyweight子類;
- FlyweightFactory:
享元工廠,負責管理享元對象池和建立享元對象。
據此我們可以寫出享元模式的基礎代碼:
Flyweight.class
public interface Flyweight { void operation();}
ConcreteFlyweight.class
public class ConcreteFlyweight implements Flyweight{ private String intrinsicState; public ConcreteFlyweight(String state) { intrinsicState = state; } @Override public void operation() { Log.e("Shawn", "ConcreteFlyweight----" + intrinsicState); }}
FlyweightFactory.class
public class FlyweightFactory { private HashMap<String, Flyweight> mFlyweights = new HashMap<>(); public Flyweight getFlyweight(String key) { Flyweight flyweight = mFlyweights.get(key); if (flyweight == null) { flyweight = new ConcreteFlyweight(key); mFlyweights.put(key, flyweight); } return flyweight; }}
測試代碼
Flyweight flyweight1 = factory.getFlyweight("a");Flyweight flyweight2 = factory.getFlyweight("b");Flyweight flyweight3 = factory.getFlyweight("a");Log.e("Shawn", "flyweight1==flyweight2 : " + (flyweight1 == flyweight2));Log.e("Shawn", "flyweight1==flyweight3 : " + (flyweight1 == flyweight3));break;
結果
com.android.flyweightpattern E/Shawn: flyweight1==flyweight2 : falsecom.android.flyweightpattern E/Shawn: flyweight1==flyweight3 : true
可以很明顯的看出 flyweight1 和 flyweight3 對象是同樣一個享元對象。
Java 中的享元模式
在 Java 中,最經典使用享元模式的案例就應該是 String 了,String 存在常量池中,也就是說一個 String 被定義之後它就被緩衝到了常量池中,當其他地方要使用同樣的字串時,則直接使用該緩衝,而不會重複建立(這也就是 String 的不可變性:java/android 設計模式學習筆記(11)—原型模式)。
比如下面的代碼:
String str1 = new String("abc");String str2 = new String("abc");String str3 = "abc";String str4 = "ab" + "c";str1 == str2; //falsestr3 == str4; //true
str1 和 str2 是兩個不同的對象,這個應該顯而易見,而 str3 和 str4 由雩都是使用的 String 享元池,所以他們兩個是同一個對象。
樣本與源碼
我們這以一個圖形系統為例,用來畫不同顏色的圓形:
Shape.class用來定義一個圖形的基本行為:
public interface Shape { void draw();}
Circle.class Shape 的實現子類,用來畫圓形:
public class Circle implements Shape{ String color; public Circle(String color) { this.color = color; } @Override public void draw() { Log.e("Shawn", "畫了一個" + color +"的圓形"); }}
ShapeFactory.class 圖形享元工廠類:
public class ShapeFactory { private HashMap<String, Shape> shapes = new HashMap<>(); public Shape getShape(String color) { Shape shape = shapes.get(color); if (shape == null) { shape = new Circle(color); shapes.put(color, shape); } return shape; } public int getSize() { return shapes.size(); }}
測試代碼
Shape shape1 = factory.getShape("紅色");shape1.draw();Shape shape2 = factory.getShape("灰色");shape2.draw();Shape shape3 = factory.getShape("綠色");shape3.draw();Shape shape4 = factory.getShape("紅色");shape4.draw();Shape shape5 = factory.getShape("灰色");shape5.draw();Shape shape6 = factory.getShape("灰色");shape6.draw();Log.e("Shawn", "一共繪製了"+factory.getSize()+"中顏色的圓形");
最後運行結果
從結果可以看到,同一個顏色的圖形共用一個對象,總共只建立了 3 個對象。
總結
享元模式實現比較簡單,但是它的作用在某些情境確實極其重要。它可以大大減少應用程式建立對象的數量和頻率,降低程式記憶體的佔用,增強程式的效能,但它同時也增加了系統的複雜性,需要分離出外部狀態和內部狀態,內部狀態為不變的共用部分,儲存於享元對象內部;而外部狀態具有固化特性,應當由用戶端來負責,不應該隨著內部狀態改變而改變,否則會導致系統的邏輯混亂。
享元模式優點:
- 能夠極大的減少系統中對象的個數;
- 享元模式由於使用了外部狀態,外部狀態相對獨立,不會影響到內部狀態,所以享元模式使得享元對象能夠在不同的環境中被共用。
享元模式缺點:
- 由於享元模式需要區分外部狀態和內部狀態,使得應用程式在某種程度上來說更加複雜化了;
- 為了使對象可以共用,享元模式需要將享元對象的狀態外部化,而讀取外部狀態使得已耗用時間變長。
討論
我在查閱相關書籍和網路資料的過程中,看到有些文章會把 Android 中的 MessagePool 定義為享元模式,但是對比了對象池模式和享元模式之後,我更傾向於認為它是對象池模式,因為從上面介紹的對比來看,MessagePool 中對象池有初始化的 size,每次從 MessagePool 中去 obtain Message 對象的時候,擷取的都是一個初始對象,其中的狀態都需要去根據需求變化,而享元模式則更傾向於重用具有相同狀態的對象,這個對象著重於在應用的每個使用地方它的狀態都具有相同性,從這個原則來看就已經排除是享元模式了,不過還是個人的看法,有沒有大神指導一下,不勝感激~~~~
源碼下載
https://github.com/zhaozepeng/Design-Patterns/tree/master/FlyweightPattern
引用
http://stackoverflow.com/questions/9322141/flyweight-vs-object-pool-patterns-when-is-each-useful
http://blog.csdn.net/jason0539/article/details/22908915
https://en.wikipedia.org/wiki/Flyweight_pattern
http://www.cnblogs.com/qianxudetianxia/archive/2011/08/10/2133659.html
http://blog.csdn.net/chenssy/article/details/11850107
java/android 設計模式學習筆記(13)---享元模式