C++設計模式——享元模式


什么是享元模式?

在GOF的《設計模式:可復用面向對象軟件的基礎》一書中對享元模式是這樣說的:運用共享技術有效地支持大量細粒度的對象。

就如上面說的棋子,如果每個棋子都new一個對象,就會存在大量細粒度的棋子對象,這對服務器的內存空間是一種考驗,也是一種浪費。我們都知道,比如我在2013號房間和別人下五子棋,2014號房間也有人在下五子棋,並不會因為我在2013號房間,而別人在2014號房間,而導致我們的棋子是不一樣的。這就是說,2013號房間和2014號房間的棋子都是一樣的,所有的五子棋房間的棋子都是一樣的。唯一的不同是每個棋子在不同的房間的不同棋盤的不同位置上。所以,對於棋子來說,我們不用放一個棋子就new一個棋子對象,只需要在需要的時候,去請求獲得對應的棋子對象,如果沒有,就new一個棋子對象;如果有了,就直接返回棋子對象。這里以五子棋為例子,進行分析,當玩家在棋盤上放入第一個白色棋子時,此時由於沒有白色棋子,所以就new一個白色棋子;當另一個玩家放入第一個黑色棋子時,此時由於沒有黑色棋子,所以就需要new一個黑色棋子;當玩家再次放入一個白色棋子時,就去查詢是否有已經存在的白色棋子對象,由於第一次已經new了一個白色棋子對象,所以,現在不會再次new一個白色棋子對象,而是返回以前new的白色棋子對象;對於黑色棋子,亦是同理;獲得了棋子對象,我們只需要設置棋子的不同棋盤位置即可。

 

UML類圖

Flyweight:描述一個接口,通過這個接口flyweight可以接受並作用於外部狀態;

ConcreteFlyweight:實現Flyweight接口,並為定義了一些內部狀態,ConcreteFlyweight對象必須是可共享的;同時,它所存儲的狀態必須是內部的;即,它必須獨立於ConcreteFlyweight對象的場景;

UnsharedConcreteFlyweight:並非所有的Flyweight子類都需要被共享。Flyweight接口使共享成為可能,但它並不強制共享。

FlyweightFactory:創建並管理flyweight對象。它需要確保合理地共享flyweight;當用戶請求一個flyweight時,FlyweightFactory對象提供一個已創建的實例,如果請求的實例不存在的情況下,就新創建一個實例;

Client:維持一個對flyweight的引用;同時,它需要計算或存儲flyweight的外部狀態。

 

實現要點

根據我們的經驗,當要將一個對象進行共享時,就需要考慮到對象的狀態問題了;不同的客戶端獲得共享的對象之后,可能會修改共享對象的某些狀態;大家都修改了共享對象的狀態,那么就會出現對象狀態的紊亂。對於享元模式,在實現時一定要考慮到共享對象的狀態問題。那么享元模式是如何實現的呢?

在享元模式中,有兩個非常重要的概念:內部狀態和外部狀態。

內部狀態存儲於flyweight中,它包含了獨立於flyweight場景的信息,這些信息使得flyweight可以被共享。而外部狀態取決於flyweight場景,並根據場景而變化,因此不可共享。用戶對象負責在必要的時候將外部狀態傳遞給flyweight。

flyweight執行時所需的狀態必定是內部的或外部的。內部狀態存儲於ConcreteFlyweight對象之中;而外部對象則由Client對象存儲或計算。當用戶調用flyweight對象的操作時,將該狀態傳遞給它。同時,用戶不應該直接對ConcreteFlyweight類進行實例化,而只能從FlyweightFactory對象得到ConcreteFlyweight對象,這可以保證對它們適當地進行共享;由於共享一個實例,所以在創建這個實例時,就可以考慮使用單例模式來進行實現。

享元模式的工廠類維護了一個實例列表,這個列表中保存了所有的共享實例;當用戶從享元模式的工廠類請求共享對象時,首先查詢這個實例表,如果不存在對應實例,則創建一個;如果存在,則直接返回對應的實例。

 

代碼實現

  1 #include <iostream>
  2 #include <map>
  3 #include <vector>
  4 using namespace std;
  5 
  6 typedef struct pointTag
  7 {
  8     int x;
  9     int y;
 10 
 11     pointTag(){}
 12     pointTag(int a, int b)
 13     {
 14         x = a;
 15     y = b;
 16     }
 17 
 18     bool operator <(const pointTag& other) const
 19     {
 20         if (x < other.x)
 21         {
 22             return true;
 23         }
 24         else if (x == other.x)
 25         {
 26             return y < other.y;
 27         }
 28 
 29         return false;
 30     }
 31 }POINT;
 32 
 33 typedef enum PieceColorTag
 34 {
 35     BLACK,
 36     WHITE
 37 }PIECECOLOR;
 38 
 39 class CPiece
 40 {
 41 public:
 42     CPiece(PIECECOLOR color) : m_color(color){}
 43     PIECECOLOR GetColor() { return m_color; }
 44 
 45     // Set the external state
 46     void SetPoint(POINT point) { m_point = point; }
 47     POINT GetPoint() { return m_point; }
 48 
 49 protected:
 50     // Internal state
 51     PIECECOLOR m_color;
 52 
 53     // external state
 54     POINT m_point;
 55 };
 56 
 57 class CGomoku : public CPiece
 58 {
 59 public:
 60     CGomoku(PIECECOLOR color) : CPiece(color){}
 61 };
 62 
 63 class CPieceFactory
 64 {
 65 public:
 66     CPiece *GetPiece(PIECECOLOR color)
 67     {
 68         CPiece *pPiece = NULL;
 69     if (m_vecPiece.empty())
 70     {
 71         pPiece = new CGomoku(color);
 72         m_vecPiece.push_back(pPiece);
 73     }
 74     else
 75     {
 76         for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
 77         {
 78             if ((*it)->GetColor() == color)
 79         {
 80             pPiece = *it;
 81             break;
 82         }
 83         }
 84         if (pPiece == NULL)
 85         {
 86         pPiece = new CGomoku(color);
 87         m_vecPiece.push_back(pPiece);
 88         }
 89      }
 90         return pPiece;
 91     }    
 92 
 93     ~CPieceFactory()
 94     {
 95         for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
 96         {
 97             if (*it != NULL)
 98         {
 99         delete *it;
100         *it = NULL;
101         }
102     }
103     }
104 
105 private:
106     vector<CPiece *> m_vecPiece;
107 };
108 
109 class CChessboard
110 {
111 public:
112     void Draw(CPiece *piece)
113     {
114     if (piece->GetColor())
115     {
116             cout<<"Draw a White"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
117     }
118     else
119     {
120         cout<<"Draw a Black"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
121     }
122     m_mapPieces.insert(pair<POINT, CPiece *>(piece->GetPoint(), piece));
123     }
124 
125     void ShowAllPieces()
126     {
127         for (map<POINT, CPiece *>::iterator it = m_mapPieces.begin(); it != m_mapPieces.end(); ++it)
128     {
129             if (it->second->GetColor())
130         {
131         cout<<"("<<it->first.x<<","<<it->first.y<<") has a White chese."<<endl;
132         }
133         else
134         {
135         cout<<"("<<it->first.x<<","<<it->first.y<<") has a Black chese."<<endl;
136         }
137     }
138     }
139 
140 private:
141     map<POINT, CPiece *> m_mapPieces;
142 };
143 
144 int main()
145 {
146     CPieceFactory *pPieceFactory = new CPieceFactory();
147     CChessboard *pCheseboard = new CChessboard();
148 
149     // The player1 get a white piece from the pieces bowl
150     CPiece *pPiece = pPieceFactory->GetPiece(WHITE);
151     pPiece->SetPoint(POINT(2, 3));
152     pCheseboard->Draw(pPiece);
153 
154     // The player2 get a black piece from the pieces bowl
155     pPiece = pPieceFactory->GetPiece(BLACK);
156     pPiece->SetPoint(POINT(4, 5));
157     pCheseboard->Draw(pPiece);
158 
159     // The player1 get a white piece from the pieces bowl
160     pPiece = pPieceFactory->GetPiece(WHITE);
161     pPiece->SetPoint(POINT(2, 4));
162     pCheseboard->Draw(pPiece);
163 
164     // The player2 get a black piece from the pieces bowl
165     pPiece = pPieceFactory->GetPiece(BLACK);
166     pPiece->SetPoint(POINT(3, 5));
167     pCheseboard->Draw(pPiece);
168 
169     /*......*/
170 
171     //Show all cheses
172     cout<<"Show all cheses"<<endl;
173     pCheseboard->ShowAllPieces();
174 
175     if (pCheseboard != NULL)
176     {
177      delete pCheseboard;
178     pCheseboard = NULL;
179     }
180     if (pPieceFactory != NULL)
181     {
182     delete pPieceFactory;
183     pPieceFactory = NULL;
184     }
185 }

內部狀態包括棋子的顏色,外部狀態包括棋子在棋盤上的位置。最終,我們省去了多個實例對象存儲棋子顏色的空間,從而達到了空間的節約。

在上面的代碼中,我建立了一個CCheseboard用於表示棋盤,棋盤類中保存了放置的黑色棋子和白色棋子;這就相當於在外部保存了共享對象的外部狀態;對於棋盤對象,我們是不是又可以使用享元模式呢?再設計一個棋局類進行管理棋盤上的棋子布局,用來保存外部狀態。對於這個,這里不進行討論了。

 

優點

享元模式可以避免大量非常相似對象的開銷。在程序設計時,有時需要生成大量細粒度的類實例來表示數據。如果能發現這些實例數據除了幾個參數外基本都是相同的,使用享元模式就可以大幅度地減少對象的數量。

 

使用場合

Flyweight模式的有效性很大程度上取決於如何使用它以及在何處使用它。當以下條件滿足時,我們就可以使用享元模式了。

  1. 一個應用程序使用了大量的對象;
  2. 完全由於使用大量的對象,造成很大的存儲開銷;
  3. 對象的大多數狀態都可變為外部狀態;
  4. 如果刪除對象的外部狀態,那么可以用相對較少的共享對象取代很多組對象。

 

擴展

之前總結了組合模式組合模式,現在回過頭來看看,享元模式就好比在組合模式的基礎上加上了一個工廠類,進行共享控制。是的,組合模式有的時候會產生很多細粒度的對象,很多時候,我們會將享元模式和組合模式進行結合使用。

 

總結

使用享元模式可以避免大量相似對象的開銷,減小了空間消耗;而空間的消耗是由以下幾個因素決定的:

  1. 實例對象減少的數目;
  2. 對象內部狀態的數目;對象內部狀態越多,消耗的空間也會越少;
  3. 外部狀態是計算的還是存儲的;由於外部狀態可能需要存儲,如果外部狀態存儲起來,那么空間的節省就不會太多。

共享的Flyweight越多,存儲節約也就越多,節約量隨着共享狀態的增多而增大。當對象使用大量的內部及外部狀態,並且外部狀態是計算出來的而非存儲的時候,節約量將達到最大。所以,可以使用兩種方法來節約存儲:用共享減少內部狀態的消耗;用計算時間換取對外部狀態的存儲。

同時,在實現的時候,一定要控制好外部狀態與共享對象的對應關系,比如我在代碼實現部分,在CCheseboard類中使用了一個map進行彼此之間的映射,這個映射在實際開發中需要考慮的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM