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