结构型模式—享元模式
Posted 浅墨浓香
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结构型模式—享元模式相关的知识,希望对你有一定的参考价值。
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", 结构型模式之享元