C++設計模式編程之Flyweight享元模式結構詳解
由遇到的問題引出享元模式:
在面向對象系統(tǒng)的設計何實現中,創(chuàng)建對象是最為常見的操作。這里面就有一個問題:如果一個應用程序使用了太多的對象,就會造成很大的存儲開銷。特別是對于大量輕量級(細粒度)的對象,比如在文檔編輯器的設計過程中,我們如果為沒有字母創(chuàng)建一個對象的話,系統(tǒng)可能會因為大量的對象而造成存儲開銷的浪費。例如一個字母"a"在文檔中出現了100000 次,而實際上我們可以讓這一萬個字母"a"共享一個對象,當然因為在不同的位置可能字母"a"有不同的顯示效果(例如字體和大小等設置不同),在這種情況我們可以為將對象的狀態(tài)分為"外部狀態(tài)"和"內部狀態(tài)",將可以被共享(不會變化)的狀態(tài)作為內部狀態(tài)存儲在對象中,而外部對象(例如上面提到的字體、大小等)我們可以在適當的時候將外部對象最為參數傳遞給對象(例如在顯示的時候,將字體、大小等信息傳遞給對象)。
作用:運用共享技術有效地支持大量細粒度的對象。
內部狀態(tài)intrinsic和外部狀態(tài)extrinsic:
1)Flyweight模式中,最重要的是將對象分解成intrinsic和extrinsic兩部分。
2)內部狀態(tài):在享元對象內部并且不會隨環(huán)境改變而改變的共享部分,可以稱為是享元對象的內部狀態(tài)
3)外部狀態(tài):而隨環(huán)境改變而改變的,取決于應用環(huán)境,或是實時數據,這些不可以共享的東西就是外部狀態(tài)了。
4)內部狀態(tài)和外部狀態(tài)之間的區(qū)別:
在Flyweight模式應用中,通常修改的是外部狀態(tài)屬性,而內部狀態(tài)屬性一般都是用于參考或計算時引用。
Flyweight執(zhí)行時所需的狀態(tài)必定是內部的或外部的。內部狀態(tài)存儲于ConcreteFlyweight對象之中;而外部狀態(tài)則由Client對象存儲或計算。當用戶調用Flyweight對象的操作時,將該狀態(tài)傳遞給它。
以文字處理軟件為例:
內部狀態(tài)存儲于flyweight中,它包含了獨立于flyweight場景的信息,這些信息使得flyweight可以被共享。如字符代碼,字符大小……
外部狀態(tài)取決于flyweight場景,并根據場景而變化,因此不可共享。用戶對象負責在必要的時候將外部狀態(tài)傳遞給flyweight,如字符位置,字符顏色……
UML圖:
解析:
Flyweight:享元類的基類,定義一個接口,通過這個接口Flyweight可以接受并作用于外部狀態(tài)。
ConcreteFlyweight:實現Flyweight接口, 并為內部狀態(tài)( 如果有的話) 增加存儲空間。ConcreteFlyweight對象必須是可共享的。它所存儲的狀態(tài)必須是內部的(intrinsic);即,它必須獨立于ConcreteFlyweight對象的場景。
UnsharedConcreteFlyweight:并非所有的Flyweight子類都需要被共享。Flyweight接口使共享成為可能,但它并不強制共享。在Flyweight對象結構的某些層次,UnsharedConcreteFlyweight對象通常將ConcreteFlyweight對象作為子節(jié)點。
FlyweightFactory:
1) 創(chuàng)建并管理Flyweight對象。
2)確保合理地共享Flyweight。當用戶請求一個Flyweight時,FlyweightFactory對象提供一個已創(chuàng)建的實例或者創(chuàng)建一個(如果不存在的話)
Client
1)維持一個對Flyweight的引用。
2)計算或存儲一個(多個)Flyweight的外部狀態(tài)。
分析:
享元模式可以避免大量非常相似類的開銷。在程序設計中,有時需要生成大量細粒度的類實例來表示數據。如果能發(fā)現這些實例數據除了幾個參數外基本都是相同的。有時就能夠大幅度地減少實例化的類的數量。如果能把那些參數移到類實例的外面,在方法調用時將它們傳遞進來,就可以通過共享大幅度地減少單個實例的數目。
比如在文檔編輯器的設計過程中,我們如果為沒有字母創(chuàng)建一個對象的話,系統(tǒng)可能會因為大量的對象而造成存儲開銷的浪費。例如一個字母“a”在文檔中出現了100000次,而實際上我們可以讓這一萬個字母“a”共享一個對象,當然因為在不同的位置可能字母“a”有不同的顯示效果(例如字體和大小等設置不同),在這種情況我們可以為將對象的狀態(tài)分為“外部狀態(tài)”和“內部狀態(tài)”,將可以被共享(不會變化)的狀態(tài)作為內部狀態(tài)存儲在對象中,而外部對象(例如上面提到的字體、大小等)我們可以在適當的時候將外部對象最為參數傳遞給對象(例如在顯示的時候,將字體、大小等信息傳遞給對象)。
Flyweight的內部狀態(tài)是用來共享的,Flyweightfactory負責維護一個Flyweight池(存放內部狀態(tài)的對象),當客戶端請求一個共享Flyweight時,這個factory首先搜索池中是否已經有可適用的,如果有,factory只是簡單返回送出這個對象,否則,創(chuàng)建一個新的對象,加入到池中,再返回送出這個對象.池為重復或可共享的對象、屬性設置一個緩沖,稱為內部狀態(tài)。這些內部狀態(tài)一般情況下都是不可修改的,也就是在第一個對象、屬性被創(chuàng)建后,就不會去修改了(否則就沒意義了)。
Flyweight 對對象的內部狀態(tài)進行共享,只為每種內部狀態(tài)創(chuàng)建一個實例,對內部狀態(tài)使用了單例模式。
用戶不應直接對ConcreteFlyweight類進行實例化,而只能從FlyweightFactory對象得到ConcreteFlyweight對象,這可以保證對它們適當地進行共享。
存儲節(jié)約由以下幾個因素決定:
1) 因為共享,實例總數減少的數目
2) 對象內部狀態(tài)的平均數目
3) 外部狀態(tài)是計算的還是存儲的
實現要點
1)享元工廠維護一張享元實例表。
2)享元不可共享的狀態(tài)需要在外部維護。即刪除外部狀態(tài):該模式的可用性在很大程度上取決于是否容易識別外部狀態(tài)并將它從共享對象中刪除。
3)按照需求可以對享元角色進行抽象。
4)管理共享對象:引用計數和垃圾回收……
何時采用
1)如果一個應用程序使用了大量的對象,而大量的這些對象造成了很大的存儲開銷時就應該考慮使用;
2)還有就是對象的大多數狀態(tài)可變?yōu)橥獠繝顟B(tài),如果刪除對象的外部狀態(tài),那么可以用相對較少的共享對象取代很多組對象,此時可以考慮所使用享元模式。
3)系統(tǒng)中有大量耗費了大量內存的細粒度對象,并且對外界來說這些對沒有任何差別的(或者說經過改造后可以是沒有差別的)。
在文檔編輯器例子中如果一個字符對應一個對象,那么一篇文檔所要容納的對象將是非常的龐大耗費大量的內存。而實際組成文檔的字符是有限的,是由這些字符不同的組合和排列得到的。所以需要共享,將基本的字符進行共享,將使得字符對象變得有限。
示例:
享元模式在編輯器系統(tǒng)中大量使用。一個文本編輯器往往會提供很多種字體,而通常的做法就是將每一個字母做成一個享元對象。享元對象的內蘊狀態(tài)就是這個字母,而字母在文本中的位置和字模風格等其他信息則是外蘊狀態(tài)。比如,字母a可能出現在文本的很多地方,雖然這些字母a的位置和字模風格不同,但是所有這些地方使用的都是同一個字母對象。這樣一來,字母對象就可以在整個系統(tǒng)中共享。
// Flyweight pattern -- Real World example using System; using System.Collections; namespace DoFactory.GangOfFour.Flyweight.RealWorld { // MainApp test application class MainApp { static void Main() { // Build a document with text string document = "AAZZBBZB"; char[] chars = document.ToCharArray(); CharacterFactory f = new CharacterFactory(); // extrinsic state int pointSize = 10; // For each character use a flyweight object foreach (char c in chars) { pointSize++; Character character = f.GetCharacter(c); character.Display(pointSize); } // Wait for user Console.Read(); } } // "FlyweightFactory" class CharacterFactory { private Hashtable characters = new Hashtable(); public Character GetCharacter(char key) { // Uses "lazy initialization" Character character = characters[key] as Character; if (character == null) { switch (key) { case 'A': character = new CharacterA(); break; case 'B': character = new CharacterB(); break; // case 'Z': character = new CharacterZ(); break; } characters.Add(key, character); } return character; } } // "Flyweight" abstract class Character { protected char symbol; protected int width; protected int height; protected int ascent; protected int descent; protected int pointSize; public abstract void Display(int pointSize); } // "ConcreteFlyweight" class CharacterA : Character { // Constructor public CharacterA() { this.symbol = 'A'; this.height = 100; this.width = 120; this.ascent = 70; this.descent = 0; } public override void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } } // "ConcreteFlyweight" class CharacterB : Character { // Constructor public CharacterB() { this.symbol = 'B'; this.height = 100; this.width = 140; this.ascent = 72; this.descent = 0; } public override void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } } // C, D, E, etc. // "ConcreteFlyweight" class CharacterZ : Character { // Constructor public CharacterZ() { this.symbol = 'Z'; this.height = 100; this.width = 100; this.ascent = 68; this.descent = 0; } public override void Display(int pointSize) { this.pointSize = pointSize; Console.WriteLine(this.symbol + " (pointsize " + this.pointSize + ")"); } } }
Output:
享元模式的優(yōu)點和缺點
享元模式的優(yōu)點在于它大幅度地降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:
享元模式使得系統(tǒng)更加復雜。為了使對象可以共享,需要將一些狀態(tài)外部化,這使得程序的邏輯復雜化。
享元模式將享元對象的狀態(tài)外部化,而讀取外部狀態(tài)使得運行時間稍微變長。
欄 目:C語言
下一篇:C++設計模式編程中簡單工廠與工廠方法模式的實例對比
本文標題:C++設計模式編程之Flyweight享元模式結構詳解
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/2421.html
您可能感興趣的文章
- 04-02c語言沒有round函數 round c語言
- 01-10深入理解C++中常見的關鍵字含義
- 01-10使用C++實現全排列算法的方法詳解
- 01-10c++中inline的用法分析
- 01-10用C++實現DBSCAN聚類算法
- 01-10全排列算法的非遞歸實現與遞歸實現的方法(C++)
- 01-10C++大數模板(推薦)
- 01-10淺談C/C++中的static與extern關鍵字的使用詳解
- 01-10深入C/C++浮點數在內存中的存儲方式詳解
- 01-10深入理解C/C++混合編程


閱讀排行
本欄相關
- 04-02c語言函數調用后清空內存 c語言調用
- 04-02func函數+在C語言 func函數在c語言中
- 04-02c語言的正則匹配函數 c語言正則表達
- 04-02c語言用函數寫分段 用c語言表示分段
- 04-02c語言中對數函數的表達式 c語言中對
- 04-02c語言編寫函數冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數 round c語言
- 04-02c語言分段函數怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數 c語言中怎
- 04-02c語言調用函數求fibo C語言調用函數求
隨機閱讀
- 01-10C#中split用法實例總結
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10delphi制作wav文件的方法
- 08-05dedecms(織夢)副欄目數量限制代碼修改
- 01-11ajax實現頁面的局部加載
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 04-02jquery與jsp,用jquery