C++設計模式之享元模式
前言
無聊的時候,也去QQ游戲大廳玩五子棋或者象棋;作為程序員,看到一個產(chǎn)品,總要去想想它是怎么設計的,怎么完成的,我想這個是所有程序員都會做的事情吧(強迫癥???)。有的時候,想完了,還要做一個DEMO出來,才能體現(xiàn)自己的NB,然后還有點小成就感。
在玩五子棋或象棋的時候,我就想過,騰訊那幫伙計是怎么做的呢?五子棋的棋子有黑白兩色,難道每次放一個棋子就new一個對象么?象棋有車、馬、相、士、帥、炮和兵,是不是每盤棋都要把所有的棋子都new出來呢?如果真的是每一個棋子都new一個,那么再加上那么多人玩;那要new多少對象啊,如果是這樣做的話,我想有多少服務器都是搞不定的,可能QQ游戲大廳會比12306還糟糕。那騰訊那幫伙計是如何實現(xiàn)的呢?那就要說到今天總結(jié)的享元模式了。
什么是享元模式?
在GOF的《設計模式:可復用面向?qū)ο筌浖幕A》一書中對享元模式是這樣說的:運用共享技術(shù)有效地支持大量細粒度的對象。
就如上面說的棋子,如果每個棋子都new一個對象,就會存在大量細粒度的棋子對象,這對服務器的內(nèi)存空間是一種考驗,也是一種浪費。我們都知道,比如我在2013號房間和別人下五子棋,2014號房間也有人在下五子棋,并不會因為我在2013號房間,而別人在2014號房間,而導致我們的棋子是不一樣的。這就是說,2013號房間和2014號房間的棋子都是一樣的,所有的五子棋房間的棋子都是一樣的。唯一的不同是每個棋子在不同的房間的不同棋盤的不同位置上。所以,對于棋子來說,我們不用放一個棋子就new一個棋子對象,只需要在需要的時候,去請求獲得對應的棋子對象,如果沒有,就new一個棋子對象;如果有了,就直接返回棋子對象。這里以五子棋為例子,進行分析,當玩家在棋盤上放入第一個白色棋子時,此時由于沒有白色棋子,所以就new一個白色棋子;當另一個玩家放入第一個黑色棋子時,此時由于沒有黑色棋子,所以就需要new一個黑色棋子;當玩家再次放入一個白色棋子時,就去查詢是否有已經(jīng)存在的白色棋子對象,由于第一次已經(jīng)new了一個白色棋子對象,所以,現(xiàn)在不會再次new一個白色棋子對象,而是返回以前new的白色棋子對象;對于黑色棋子,亦是同理;獲得了棋子對象,我們只需要設置棋子的不同棋盤位置即可。
UML類圖
Flyweight:描述一個接口,通過這個接口flyweight可以接受并作用于外部狀態(tài);
ConcreteFlyweight:實現(xiàn)Flyweight接口,并為定義了一些內(nèi)部狀態(tài),ConcreteFlyweight對象必須是可共享的;同時,它所存儲的狀態(tài)必須是內(nèi)部的;即,它必須獨立于ConcreteFlyweight對象的場景;
UnsharedConcreteFlyweight:并非所有的Flyweight子類都需要被共享。Flyweight接口使共享成為可能,但它并不強制共享。
FlyweightFactory:創(chuàng)建并管理flyweight對象。它需要確保合理地共享flyweight;當用戶請求一個flyweight時,F(xiàn)lyweightFactory對象提供一個已創(chuàng)建的實例,如果請求的實例不存在的情況下,就新創(chuàng)建一個實例;
Client:維持一個對flyweight的引用;同時,它需要計算或存儲flyweight的外部狀態(tài)。
實現(xiàn)要點
根據(jù)我們的經(jīng)驗,當要將一個對象進行共享時,就需要考慮到對象的狀態(tài)問題了;不同的客戶端獲得共享的對象之后,可能會修改共享對象的某些狀態(tài);大家都修改了共享對象的狀態(tài),那么就會出現(xiàn)對象狀態(tài)的紊亂。對于享元模式,在實現(xiàn)時一定要考慮到共享對象的狀態(tài)問題。那么享元模式是如何實現(xiàn)的呢?
在享元模式中,有兩個非常重要的概念:內(nèi)部狀態(tài)和外部狀態(tài)。
內(nèi)部狀態(tài)存儲于flyweight中,它包含了獨立于flyweight場景的信息,這些信息使得flyweight可以被共享。而外部狀態(tài)取決于flyweight場景,并根據(jù)場景而變化,因此不可共享。用戶對象負責在必要的時候?qū)⑼獠繝顟B(tài)傳遞給flyweight。
flyweight執(zhí)行時所需的狀態(tài)必定是內(nèi)部的或外部的。內(nèi)部狀態(tài)存儲于ConcreteFlyweight對象之中;而外部對象則由Client對象存儲或計算。當用戶調(diào)用flyweight對象的操作時,將該狀態(tài)傳遞給它。同時,用戶不應該直接對ConcreteFlyweight類進行實例化,而只能從FlyweightFactory對象得到ConcreteFlyweight對象,這可以保證對它們適當?shù)剡M行共享;由于共享一個實例,所以在創(chuàng)建這個實例時,就可以考慮使用單例模式來進行實現(xiàn)。
享元模式的工廠類維護了一個實例列表,這個列表中保存了所有的共享實例;當用戶從享元模式的工廠類請求共享對象時,首先查詢這個實例表,如果不存在對應實例,則創(chuàng)建一個;如果存在,則直接返回對應的實例。
代碼實現(xiàn):
#include <iostream>
#include <map>
#include <vector>
using namespace std;
typedef struct pointTag
{
int x;
int y;
pointTag(){}
pointTag(int a, int b)
{
x = a;
y = b;
}
bool operator <(const pointTag& other) const
{
if (x < other.x)
{
return true;
}
else if (x == other.x)
{
return y < other.y;
}
return false;
}
}POINT;
typedef enum PieceColorTag
{
BLACK,
WHITE
}PIECECOLOR;
class CPiece
{
public:
CPiece(PIECECOLOR color) : m_color(color){}
PIECECOLOR GetColor() { return m_color; }
// Set the external state
void SetPoint(POINT point) { m_point = point; }
POINT GetPoint() { return m_point; }
protected:
// Internal state
PIECECOLOR m_color;
// external state
POINT m_point;
};
class CGomoku : public CPiece
{
public:
CGomoku(PIECECOLOR color) : CPiece(color){}
};
class CPieceFactory
{
public:
CPiece *GetPiece(PIECECOLOR color)
{
CPiece *pPiece = NULL;
if (m_vecPiece.empty())
{
pPiece = new CGomoku(color);
m_vecPiece.push_back(pPiece);
}
else
{
bool bFound = false; // 非常感謝fireace指出的問題
for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
{
if ((*it)->GetColor() == color)
{
bFound = true;
pPiece = *it;
break;
}
bFound = false;
}
if (!bFound)
{
pPiece = new CGomoku(color);
m_vecPiece.push_back(pPiece);
}
}
return pPiece;
}
~CPieceFactory()
{
for (vector<CPiece *>::iterator it = m_vecPiece.begin(); it != m_vecPiece.end(); ++it)
{
if (*it != NULL)
{
delete *it;
*it = NULL;
}
}
}
private:
vector<CPiece *> m_vecPiece;
};
class CChessboard
{
public:
void Draw(CPiece *piece)
{
if (piece->GetColor())
{
cout<<"Draw a White"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
}
else
{
cout<<"Draw a Black"<<" at ("<<piece->GetPoint().x<<","<<piece->GetPoint().y<<")"<<endl;
}
m_mapPieces.insert(pair<POINT, CPiece *>(piece->GetPoint(), piece));
}
void ShowAllPieces()
{
for (map<POINT, CPiece *>::iterator it = m_mapPieces.begin(); it != m_mapPieces.end(); ++it)
{
if (it->second->GetColor())
{
cout<<"("<<it->first.x<<","<<it->first.y<<") has a White chese."<<endl;
}
else
{
cout<<"("<<it->first.x<<","<<it->first.y<<") has a Black chese."<<endl;
}
}
}
private:
map<POINT, CPiece *> m_mapPieces;
};
int main()
{
CPieceFactory *pPieceFactory = new CPieceFactory();
CChessboard *pCheseboard = new CChessboard();
// The player1 get a white piece from the pieces bowl
CPiece *pPiece = pPieceFactory->GetPiece(WHITE);
pPiece->SetPoint(POINT(2, 3));
pCheseboard->Draw(pPiece);
// The player2 get a black piece from the pieces bowl
pPiece = pPieceFactory->GetPiece(BLACK);
pPiece->SetPoint(POINT(4, 5));
pCheseboard->Draw(pPiece);
// The player1 get a white piece from the pieces bowl
pPiece = pPieceFactory->GetPiece(WHITE);
pPiece->SetPoint(POINT(2, 4));
pCheseboard->Draw(pPiece);
// The player2 get a black piece from the pieces bowl
pPiece = pPieceFactory->GetPiece(BLACK);
pPiece->SetPoint(POINT(3, 5));
pCheseboard->Draw(pPiece);
/*......*/
//Show all cheses
cout<<"Show all cheses"<<endl;
pCheseboard->ShowAllPieces();
if (pCheseboard != NULL)
{
delete pCheseboard;
pCheseboard = NULL;
}
if (pPieceFactory != NULL)
{
delete pPieceFactory;
pPieceFactory = NULL;
}
}
內(nèi)部狀態(tài)包括棋子的顏色,外部狀態(tài)包括棋子在棋盤上的位置。最終,我們省去了多個實例對象存儲棋子顏色的空間,從而達到了空間的節(jié)約。
在上面的代碼中,我建立了一個CCheseboard用于表示棋盤,棋盤類中保存了放置的黑色棋子和白色棋子;這就相當于在外部保存了共享對象的外部狀態(tài);對于棋盤對象,我們是不是又可以使用享元模式呢?再設計一個棋局類進行管理棋盤上的棋子布局,用來保存外部狀態(tài)。對于這個,這里不進行討論了。
優(yōu)點
享元模式可以避免大量非常相似對象的開銷。在程序設計時,有時需要生成大量細粒度的類實例來表示數(shù)據(jù)。如果能發(fā)現(xiàn)這些實例數(shù)據(jù)除了幾個參數(shù)外基本都是相同的,使用享元模式就可以大幅度地減少對象的數(shù)量。
使用場合
Flyweight模式的有效性很大程度上取決于如何使用它以及在何處使用它。當以下條件滿足時,我們就可以使用享元模式了。
1.一個應用程序使用了大量的對象;
2.完全由于使用大量的對象,造成很大的存儲開銷;
3.對象的大多數(shù)狀態(tài)都可變?yōu)橥獠繝顟B(tài);
4.如果刪除對象的外部狀態(tài),那么可以用相對較少的共享對象取代很多組對象。
擴展
之前總結(jié)了組合模式組合模式,現(xiàn)在回過頭來看看,享元模式就好比在組合模式的基礎上加上了一個工廠類,進行共享控制。是的,組合模式有的時候會產(chǎn)生很多細粒度的對象,很多時候,我們會將享元模式和組合模式進行結(jié)合使用。
總結(jié)
使用享元模式可以避免大量相似對象的開銷,減小了空間消耗;而空間的消耗是由以下幾個因素決定的:
1.實例對象減少的數(shù)目;
2.對象內(nèi)部狀態(tài)的數(shù)目;對象內(nèi)部狀態(tài)越多,消耗的空間也會越少;
3.外部狀態(tài)是計算的還是存儲的;由于外部狀態(tài)可能需要存儲,如果外部狀態(tài)存儲起來,那么空間的節(jié)省就不會太多。
共享的Flyweight越多,存儲節(jié)約也就越多,節(jié)約量隨著共享狀態(tài)的增多而增大。當對象使用大量的內(nèi)部及外部狀態(tài),并且外部狀態(tài)是計算出來的而非存儲的時候,節(jié)約量將達到最大。所以,可以使用兩種方法來節(jié)約存儲:用共享減少內(nèi)部狀態(tài)的消耗;用計算時間換取對外部狀態(tài)的存儲。
同時,在實現(xiàn)的時候,一定要控制好外部狀態(tài)與共享對象的對應關系,比如我在代碼實現(xiàn)部分,在CCheseboard類中使用了一個map進行彼此之間的映射,這個映射在實際開發(fā)中需要考慮的。
好了,享元模式就總結(jié)到這里了。希望大家和我分享你對設計模式的理解。我堅信:分享使我們更進步。
PS:至于騰訊那幫伙計到底是如何實現(xiàn)QQ游戲大廳的,我也不知道,這里也完全是猜測的,請不要以此為基準。
上一篇:C++設計模式之觀察者模式
欄 目:C語言
下一篇:C++中的類型轉(zhuǎn)換static_cast、dynamic_cast、const_cast和reinterpret_cas
本文標題:C++設計模式之享元模式
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/3306.html
您可能感興趣的文章
- 04-02c語言沒有round函數(shù) round c語言
- 01-10深入理解C++中常見的關鍵字含義
- 01-10使用C++實現(xiàn)全排列算法的方法詳解
- 01-10c++中inline的用法分析
- 01-10用C++實現(xiàn)DBSCAN聚類算法
- 01-10全排列算法的非遞歸實現(xiàn)與遞歸實現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關鍵字的使用詳解
- 01-10深入C/C++浮點數(shù)在內(nèi)存中的存儲方式詳解
- 01-10深入理解C/C++混合編程


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