第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)享元模式与策略模式

  可以使用享元模式来实现策略模式中的策略对象。和状态模式一样,在策略模式中也存在大量细粒度的策略对象,它们需要的数据同样从上下文传入,所以也可以使用享元模式来缓存在这些策略对象。

猜你喜欢

转载自blog.csdn.net/CherishPrecious/article/details/83987177