1. 程式人生 > >第13章 結構型模式—享元模式

第13章 結構型模式—享元模式

1. 享元模式(Flyweight Pattern)的定義

(1)運用共享技術高效地支援大量細粒度的物件

  ①物件內部狀態資料不變且重複出現,這部分不會隨環境變化而改變,是可以共享的

  ②物件外部狀態資料是變化的,會隨環境變化而改變,是不可以共享的

  ③所謂的享元,就是把內部狀態的資料分離出來共享,通過共享享元物件,可以減少對記憶體的佔用。把外部狀態分離出來,放到外部,讓應用程式在使用的時候進行維護,並在需要的時候傳遞給享元物件使用。

  ④享元模式真正快取和共享的是享元的內部狀態,而外部狀態是不被快取共享的。同時內部狀態和外部狀態是獨立的,外部狀態的變化不會影響到內部狀態。

(2)享元模式的結構和說明

 

  ①Flyweight:介面或抽象類,通過這個介面Flyweight可以接受並作用於外部狀態。通過這個介面傳入外部的狀態,在享元物件的方法處理中可能會使用這些外部的資料。

  ②ConcreteFlyweight:具體的享元實現物件,必須是可共享的,為內部狀態提供成員變數進行儲存。(真正要被共享的物件

  ③UnsharedConcreteFlyweight:非共享的享元實現物件,並不是所有的Flyweight實現物件都需要共享。非共享的享元實現物件通常是對共享享元物件的組合物件,這種物件雖然不需要加入到享元工廠中去共享,但也繼承自Flyweight,其好處是可以統一介面。注意,本例中的UnsharedConcreteFlyweight是從Flyweight繼承而來

,有時也可以不用繼承自Flyweight。(該類的作用:提供外部狀態儲存

  ④FlyweightFactory:為控制內部狀態的共享,並且讓外部能簡單地使用共享資料,提供一個工廠來管理享元,把它稱為享元工廠。其作用主要用來建立並管理共享的享元物件享元池一般設計成鍵值對。這裡也對外提供訪問共享享元的介面。

  ⑤Client:享元客戶端,主要的工作是維持一個對Flyweight的引用,計算或儲存享元物件的外部狀態。當然這裡可以訪問共享和不共享的Flyweight物件。

(3)思考享元模式

  ①享元模式的本質:分離與共享。享元模式的關鍵在於分離變與不變,把不變部分作為享元物件的內部狀態

,而變化部分則作為外部狀態,由外部來維護.這樣享元物件就能夠被共享,從而減少物件的數量,並節省大量的記憶體空間。

  ②享元模式的變與不變:為什麼在一個地方要預留介面,一個常見的原因是這裡存在變化,可能在今後需要擴充套件或改變己有的實現,而預留介面作為“可插入性的保證”

  ③享元物件:又有共享(ConcreteFlyweight)與不共享(UnsharedConcreteFlyweight)之分。不共享一般出現在和組合模式合用的情況,通常共享是葉子物件,一般不共享的部分是由共享部分組合而成,由於所有細粒度的葉子物件己經快取了,那麼快取組合物件就沒有什麼意義。(如許可權管理中某安全實體的“檢視”和“修改”是可共享的,如果將“檢視”和“修改”組合成“操作”許可權的話,則“操作”許可權是不用快取(共享)的,因為己經在細粒度上進行了快取)。(見後面的例子)

【程式設計實驗】圍棋軟體設計

  ①內部狀態:每個圍棋那麼多的棋子,可設定為享元物件,有如下屬性顏色、大小、形狀。

  ②外部狀態:棋子在棋盤中的位置。這是不可共享的。

//結構型模式:享元模式
//場景:圍棋軟體設計。
//內部狀態——圍棋棋子數量多,但分只為兩類:白棋和黑棋。
//          有顏色、大小、形態等屬性,是可共享的物件。
//外部狀態——棋子在棋盤中的位置。

#include <iostream>
#include <string>
#include <map>

using namespace std;

//************************************享元類**************************
class Coordinate; //前向宣告

//享元抽象類
class ChessFlyweight
{
public:
    virtual string& getColor() = 0;
    virtual void setColor(string color) = 0;
    
    //顯示棋子在棋盤中的位置
    //可以通過這個介面,將外部狀態傳入享元物件中
    virtual void display(Coordinate& c) = 0;
};

//非享元物件:UnsharedConcreteFlyweight(外部狀態)
class Coordinate
{
    int x,y;
public:
    Coordinate(int x, int y){this->x = x;this->y = y;}
    int getX(){return x;}
    void setX(int x){this->x = x;}

    int getY(){return y;}
    void setY(int x){this->y = y;}        
};

//享元物件:ConcreteFlyweight(內部狀態)
class ConcreteChess : public ChessFlyweight
{
private:
    string color;
public:
    ConcreteChess(string color){this->color = color;} 
    string& getColor() {return color;}
    void setColor(string color){this->color = color;}
    
    void display(Coordinate& c)
    {
        cout << "Chess's Color: " << color << endl; 
        cout << "Position: x = " << c.getX() << " y = " << c.getY() << endl;
    }
};

//************************************享元工廠類**************************
class ChessFlyweightFactory
{
private:
    static map<string,ChessFlyweight*> chessMap;
public:
    static ChessFlyweight* getChess(string color)
    {
        ChessFlyweight* ret = chessMap[color]; 
        if(ret == NULL)
        {
            ret = new ConcreteChess(color);
            chessMap[color] = ret;
        }
        return ret;   
    }

    static void clear()
    {
        map<string, ChessFlyweight*>::iterator iter = chessMap.begin();
        while(iter != chessMap.end())
        {
            delete iter->second;
            cout <<iter->second << endl;
            ++iter;
        }
        chessMap.clear();
    }    
};

map<string,ChessFlyweight*> ChessFlyweightFactory::chessMap;

int main()
{
    ChessFlyweight* chess1 = ChessFlyweightFactory::getChess("black");
    ChessFlyweight* chess2 = ChessFlyweightFactory::getChess("black");
    ChessFlyweight* chess3 = ChessFlyweightFactory::getChess("white");
    
    cout << "chess1 = " << chess1 << endl;
    cout << "chess2 = " << chess2 << endl;
    cout << "chess3 = " << chess3 << endl;
    
    //增加外部狀態的處理
    cout << "extrinsic state: " << endl;
    Coordinate c1(10, 10);
    Coordinate c2(20, 20);
    Coordinate c3(30, 30);
    
    chess1->display(c1);
    chess2->display(c2);
    chess3->display(c3);
    
    //刪除享元池中的物件
    ChessFlyweightFactory::clear();
    
    return 0;
}

2. 享元模式的物件管理

(1)實用引用計數的基本思路

  在享元工廠中定義另外一個map,它的key值與快取物件的key是一樣的,而value就是被引用的次數,這樣當外部每次獲取該享元的時候,就把對應的引用計數加1,然後再記錄回去。

(2)實現垃圾回收的基本思路

  ①確定垃圾:定義一個快取物件的配置物件,在這個物件中描述了快取的開始時間和最長不被使用時間,則當前的時間-快取開始時間≥最長不被使用使間是表示為垃圾。當然每次物件被使用時,就把那個快取開始的時間更新為使用時的當前時間

  ②回收時機:判斷出是垃圾的時候就可以回收了。誰來判斷垃圾?一般定義一個內部執行緒,這個執行緒在享元工廠被建立時啟動,每隔一定的時間來迴圈快取中所有物件的快取配置,看是否是垃圾,如果是就可以啟動回收了。

(3)怎麼回收:直接從快取的Map物件刪除相應的物件。

3. 享元模式的優缺點

(1)優點:減少物件數量,節省記憶體空間

(2)缺點:維護共享物件,需要額外開銷。如需要額外的執行緒來維護垃圾回收。

4. 使用場景

(1)系統中存在大量的相似物件

(2)細粒度的物件都具有較接近的外部狀態,而且內部狀態與環境無關,也就是說對明沒有特定的身份。

(3)需要緩衝池的場景

(4)如果不考慮物件的外部狀態,可以用相對較少的共享物件取代很多組合物件,可以使用享元模式來共享物件,然後組合物件來使用這些共享物件。

【程式設計實驗】許可權管理系統

 

//結構型模式:享元模式
//場景:許可權管理。
//幾個概念
//1.安全實體:如某個資料表
//2.許可權:指很對安全實體進行的操作,如檢視、修改、刪除等。
//幾個問題
//1.因安全實體的許可權可能被成千上萬的人共享。如“人員列表”的查詢許可權
//  為了減少建立物件,可以將“人員列表”的“查詢”許可權作為一個享元物件,放入享元工廠
//2.為了增加實用性,這裡採用享元模式+組合模式的方式實現了多層次的許可權管理。
//  組合:將安全實體的許可權進行組合,如“檢視”+“修改” = “操作”許可權,由於“操作”是組
//  合的許可權,所以無須在享元工廠中快取,即這個組合物件是個UnsharedConcreteFlyweight
//  物件。
//3. 享元工廠的垃圾回收:建立一個執行緒,專門負責過期的垃圾回收

#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <sstream>
#include <ctime>
#include <windows.h>
#include <process.h>

using namespace std;

//////////////////////////////////////////////////////////////////////////
// 字串分割
// 
// -------------------------------------------------------------------------
// 函式     : Split
// 功能     : 分割STL標準字串
// 返回值   : void 
// 引數     : Container<std::basic_string<CharT> >& v 存放分割結果
// 引數     : const std::basic_string<CharT>& s 待分割字串
// 引數     : const std::basic_string<CharT>& c 分割字串
// -------------------------------------------------------------------------
template<typename CharT, template<typename S, typename Q = std::allocator<S> > class Container>
void Split(Container<std::basic_string<CharT> >& v, const std::basic_string<CharT>& s, const std::basic_string<CharT>& c);

template<template<typename S, typename Q = std::allocator<S> > class Container>
void Split(Container<std::basic_string<char> >& v, const std::basic_string<char>& s, const std::basic_string<char>& c)
{
    if (0 == c.length())
        return;

    std::basic_string<char>::size_type pos1 = 0;
    std::basic_string<char>::size_type pos2 = 0;

    pos1 = 0;
    pos2 = s.find(c);
    while (std::basic_string<char>::npos != pos2)
    {
        v.push_back(s.substr(pos1, pos2 - pos1));

        pos1 = pos2 + c.size();
        pos2 = s.find(c, pos1);
    }

    if (pos1 != s.length())
    {
        v.push_back(s.substr(pos1));
    }
}

//*******************************************輔助類******************************
//測試資料(在記憶體中模擬資料庫中的值)
class TestDB
{
private:
    TestDB(){}
public:
    //用來存放單獨授權資料的值
    static vector<string> vectorDB;

    //用來存放組合授權資料的值
    //其中的key為組合資料的id,value為該組合包含的多條授權資料的值
    static map<string, vector<string> > mapDB;
};
//單獨授權資料
static vector<string>::value_type init_value[] =
{
    vector<string>::value_type("ZhangSan,PersonTable,Query,1"),
    vector<string>::value_type("LiSi,PersonTable,Query,1"),
    vector<string>::value_type("LiSi,OperateSalaryTable,,2"),
    vector<string>::value_type("ZhangSan0,PersonTable,Query,1"),
    vector<string>::value_type("ZhangSan1,PersonTable,Query,1"),
    vector<string>::value_type("ZhangSan2,PersonTable,Query,1"),
};
vector<string> TestDB::vectorDB(init_value, init_value + 6);

//組合授權資料
static vector<string>::value_type initValue[] =
{
    vector<string>::value_type("SalaryTable,Query"),
    vector<string>::value_type("SalaryTable,Modify")
};
static vector<string> compositePermit(initValue, initValue + 2);
static map<string, vector<string> >::value_type initMap_value[] =
{
    map<string, vector<string> >::value_type("OperateSalaryTable", compositePermit)
};

map<string, vector<string> > TestDB::mapDB(initMap_value, initMap_value + 1);


//**********************************享元類************************
//享元介面:描述授權資料的介面
class  Flyweight
{
public:
    virtual bool match(string securityEntity, string permit) = 0;

    //享元模式與組合模式的結合。為Flyweight新增子Flyweight物件
    virtual void add(Flyweight* f) = 0;
};

//具體享元物件(封裝授權資料中重複出現的部分)
//由於add是針對組合物件的,而這個可共享的是葉子物件,所以這裡丟擲
//異常就可以了。
//享元工廠裡存放的是這個物件,如“薪資資料表的檢視許可權”。因為某一許可權
//可能被分配給成上千萬個人,所以需要快取。即享元
class AuthorizationFlyweight : public Flyweight
{
private:
    //內部狀態

    string securityEntity;//安全實體
    string permit;        //許可權

public:
    //建構函式,傳入狀態資料,包含安全實體和許可權的資料,用“,”分隔
    AuthorizationFlyweight(string state)
    {
        vector<string> v;
        Split(v, state, ",");
        securityEntity = v[0];
        permit = v[1];
    }

    string& getSecurityEntity(){ return securityEntity; }
    string& getPermit(){ return permit; }

    bool match(string securityEntity, string permit)
    {
        bool ret = (this->securityEntity == securityEntity &&
                    this->permit == permit);

        return ret;
    }

    void add(Flyweight* f)
    {
        //葉子物件,什麼都不做!這裡也可以丟擲異常!
    }
};

//不需要共享的享元物件的實現,也是組合模式中的組合物件
class UnsharedConcreteFlyweight : public Flyweight
{
private:
    //記錄每個組合物件所包含的子元件
    vector<Flyweight*> flyweights;
public:
    void add(Flyweight* f)
    {

        flyweights.push_back(f);
    }

    bool match(string securityEntity, string permit)
    {
        bool ret = false;

        vector<Flyweight*>::iterator iter = flyweights.begin();
        while (iter != flyweights.end())
        {
            if ((*iter)->match(securityEntity, permit))
            {
                ret = true;
                break;
            }
            ++iter;
        }
        return ret;
    }

};

//**************************************垃圾回收************************
//描述享元物件快取的配置物件
class CacheConfModel
{
private:

    //被引用次數
    int refCount;

    //快取開始計時的開始時間
    long beginTime;

    //快取物件存放的持續時間,其實是最長不被使用時間
    double durableTime;

    //快取物件需要被永久儲存,也就是不需要從快取中刪除
    bool forever;
public:
    CacheConfModel() :refCount(0), beginTime(0), durableTime(0), forever(false){}

    int getRefCount(){ return refCount; }
    void setRefCount(int refCount){ this->refCount = refCount; }
    int addRefCount(){ return ++refCount; }
    int decRefCount(){ return --refCount; }

    bool isForever(){ return forever; }
    void setForever(bool forever){ this->forever = forever; }

    long getBeginTime(){ return beginTime; }
    void setBeginTime(long beginTime){ this->beginTime = beginTime; }

    double getDurableTime(){ return durableTime; }
    void setDurableTime(double durableTime){ this->durableTime = durableTime; }

};

//******************************************************************
//享元工廠,通常實現為單例
//加入實現垃圾回收和引用計數的功能
class FlyweightFactory
{
private:
    //建構函式設為私有
    FlyweightFactory()
    {
        //啟動清除快取值的執行緒
        HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, clearCache, NULL, 0, NULL);

    };

    //快取多個Flyweight物件
    static map<string, Flyweight*> fsMap;

    //用來快取被共享物件的快取配置,key和上面fsMap一致
    static map<string, CacheConfModel> cacheConfMap;

    //預設儲存6秒鐘,主要為了測試方便,這個時間可以根據實際來設定
    static const long DURABLE_TIME = 6L;

    //刪除key對應的享元物件,連帶清除對應的快取配置
    void removeFlyweight(string key) //執行緒不安全,實際應用中要加同步
    {
        fsMap.erase(key);
        cacheConfMap.erase(key);
    }

    //維護清除快取的執行緒函式
    static unsigned int __stdcall clearCache(PVOID pvParam)
    {
        while (true)
        {
            map<string, CacheConfModel>::iterator iter = cacheConfMap.begin();
            vector<string> tmpKey;

            CacheConfModel ccm;
            while (iter != cacheConfMap.end())
            {
                ccm = iter->second;

                //比較是否需要清除
                if (time(NULL) - ccm.getBeginTime() >= 
                    ccm.getDurableTime())
                {
                    //可以清除,先記錄下來
                    tmpKey.push_back(iter->first);
                }
                ++iter;
            }

            //真正清除
            vector<string>::iterator iterKey = tmpKey.begin();
            while (iterKey != tmpKey.end())
            {
                (FlyweightFactory::getInstance())->removeFlyweight(*iterKey);
                ++iterKey;
            }

            //顯示
            cout << "now thread=" << fsMap.size() << " [";
            map<string, Flyweight*>::iterator fsIter = fsMap.begin();
            while (fsIter != fsMap.end())
            {
                cout << fsIter->first << ";";
                ++fsIter;
            }
            cout << "]" << endl;

            //休息1秒鐘,再重新判斷
            Sleep(1000);
        }
        return 0;
    }

public:
    static FlyweightFactory* getInstance(){
        static FlyweightFactory factory;
        return &factory;
    }

    //快取多個Flyweight物件(執行緒不安全,實際應用中要加同步)
    Flyweight* getFlyweight(string key)
    {
        Flyweight* f = fsMap[key];

        if (f == NULL)
        {
            f = new AuthorizationFlyweight(key);
            fsMap[key] = f;

            //同時設定快取配置資料和引用計數
            CacheConfModel cm;
            cm.setBeginTime((long)time(NULL));
            cm.setDurableTime(DURABLE_TIME);
            cm.setForever(false);
            cm.addRefCount();

            cacheConfMap[key] = cm;
        } else
        {
            CacheConfModel cm = cacheConfMap[key];
            cm.setBeginTime((long)time(NULL));
            cm.addRefCount();
            cacheConfMap[key] = cm;
        }
        return f;
    }

    //獲取某個享元被使用的次數(執行緒不安全,沒有同步!)
    int getUseTimes(string key)
    {
        CacheConfModel ccm = cacheConfMap[key];
        return ccm.getRefCount();
    }

};

map<string, Flyweight*>  FlyweightFactory::fsMap;
map<string, CacheConfModel>  FlyweightFactory::cacheConfMap;

//安全管理,實現成單例
class SecurityMgr
{
private:
    SecurityMgr(){}
public:
    //餓漢式
    static SecurityMgr* getInstance()
    {
        static SecurityMgr instance;
        return &instance;
    }

    //從資料庫中獲取某人所擁有的許可權
    vector<Flyweight*> queryByUser(string user)
    {
        vector<Flyweight*> ret;
        vector<string> vc;
        vector<string>::iterator iter = TestDB::vectorDB.begin();

        while (iter != TestDB::vectorDB.end())
        {
            vc.clear();
            Split(vc, *iter, ",");

            if (vc.size() <4) break;

            if (vc[0] == user)
            {

                Flyweight* fm = NULL;
                if (vc[3] == "2"){
                    //表示組合
                    fm = new UnsharedConcreteFlyweight();
                    vector<string> tempSs = TestDB::mapDB[vc[1]];
                    vector<string>::iterator iter = tempSs.begin();
                    while (iter != tempSs.end())
                    {
                        Flyweight* tempFm = (FlyweightFactory::getInstance())->getFlyweight(*iter);
                        //把這個物件加入到組合物件中
                        fm->add(tempFm);
                        ++iter;
                    }

                } else
                {
                    fm = (FlyweightFactory::getInstance())->getFlyweight(vc[1] + "," + vc[2]);
                }
                ret.push_back(fm);
            }

            ++iter;
        }

        return ret;
    }

    //判斷某使用者對某個安全實體是否擁有某種許可權
    bool hasPermit(string user, string securityEntity, string permit)
    {
        bool ret = false;
        vector<Flyweight*> vfw = queryByUser(user);

        //cout << "Now testing: " << securityEntity << "\'s " << permit << " permission, map.size = "
        //<< maps.size() << endl;

        if (vfw.size() == 0)
        {
            cout << user << ": no permit to operate " << securityEntity << endl;
            return ret;
        }

        vector<Flyweight*>::iterator iter = vfw.begin();
        while (iter != vfw.end())
        {
            cout << "am == " << (*iter) << endl;
            if ((*iter)->match(securityEntity, permit))
            {
                ret = true;
                break;
            }

            ++iter;
        }

        return ret;
    }
};

int main()
{
    //客戶端呼叫

    //需要先登入,然後再判斷是否有許可權
    SecurityMgr* mgr = SecurityMgr::getInstance();

    bool f1 = mgr->hasPermit("ZhangSan", "PersonTable", "Query");
    bool f2 = mgr->hasPermit("LiSi", "SalaryTable", "Query");
    bool f3 = mgr->hasPermit("LiSi", "SalaryTable", "Modify");

    cout << "f1 = " << f1 << endl;
    cout << "f2 = " << f2 << endl;
    cout << "f3 = " << f3 << endl;

    ostringstream oss;
    for (int i = 0; i < 3; i++){
        oss.str("");
        oss << i;
        string name = "ZhangSan" + oss.str();
        mgr->hasPermit(name, "SalaryTable", "Query");
    }

    //檢視引用次數,不是指測試使用的次數,指的是SecurityMgr裡的
    //queryByUser方法通過享元工廠去獲以享元物件的次數
    cout << "Salary, Query reference count: " << (FlyweightFactory::getInstance())->getUseTimes("SalaryTable,Query") << endl;
    cout << "Salary, Modify reference count: " << (FlyweightFactory::getInstance())->getUseTimes("SalaryTable,Modify") << endl;
    cout << "Person, Query reference count: " << (FlyweightFactory::getInstance())->getUseTimes("PersonTable,Query") << endl;

    system("pause");
    return 0;
}

5. 相關模式

(1)享元模式與組合模式

  在享元模式中,存在不需要共享的享元實現(UnsharedConcreteFlyweight),這些不需要共享的享元通常是對共享的享元物件的組合物件。也就是說,享元模式通常會和組合模式使用,來實現更復雜的物件層次結構。

(2)享元模式與狀態模式

  可以使用享元模式來共享狀態模式中的狀態物件,通常在狀態模式中,會存在數量很大的、細粒度的狀態物件,而且它們基本上是可以重複使用的,都是用來處理某一個固定的狀態的,它們需要的資料通常是由外部傳入的,也就是變化的部分分離出去了,所以可以用享元模式來實現這些狀態物件。

(3)享元模式與策略模式

  可以使用享元模式來實現策略模式中的策略物件。和狀態模式一樣,在策略模式中也存在大量細粒度的策略物件,它們需要的資料同樣從上下文傳入,所以也可以使用享元模式來快取在這些策略物件。