享元模式
享元模式的定义:运用共享技术有效的支持大量细粒度的对象。
当我们系统中某个对象类型的实例较多时,并且对这些实例进行分类后,发现真正有区别的分类很少的情况。(也就是说内存中存在着大量的看起来都差不多的对象实例)
我们就可以(分离与共享),分离这些对象中变的部分与不变的部分,然后共享不变的部分(不变并不是代表完全一样,而是变化很小,比如下面例子的authority只有查看、修改两种情况;再如文字处理程序的字体就那么几十种字体,字号就那么几个字号,它们都可以看成是不变的部分),来减少重复对象的数量。我们把不变的部分称为内部状态,变的部分称为外部状态。外部状态由外部来控制(由客户端维护),剩下的内部状态所组成的能被共享的对象就是享元对象。外部状态可以通过方法传参的方式传递给享元对象进行使用。
事实上,分离变与不变是软件设计上最基本的方式之一,比如预留接口,为什么在这个地方要预留接口,一个常见的原因就是这里存在变化,可能在今后需要扩展、或者是改变已有的实现,因此预留接口做为“可插入性的保证”。
使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。(享元池由享元工厂类提供,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。)
现在我们来举一个例子,比如系统的授权管理,我们不可能在用户每做一个操作就去数据库查看该用户有没有相应的权限(如果用户量很大且操作很频繁)。我们可以用缓存,这很好,但还有一个问题,如果数据库里配置了很多的权限,我们缓存出来的话,将会占用大量的内存。这就是享元模式的运用场景:
user authority item
员工1 查看 工资列表 员工2 查看 工资列表 员工3 查看 工资列表 人事1 查看 员工列表
经理1 查看 工资列表 经理1 修改 工资列表 经理2 删除 工资列表 经理3 修改 工资列表
缓存的权限对象里存在着大量的看起来都差不多的对象实例,我们可以分离内部状态(authority、item)外部状态(user)
我们来看一下它的结构图:
我们来看一下client里的代码:
public class Client { //存放用户权限列表的字典,可供查询 public Dictionary<string,List<Flyweight>> dic=new Dictionary<string,List<Flyweight>>(); //取到某用户的权限列表放到dic里,用户刚登陆时调用 private void GetUserAuthorityItem(string user) { List<Flyweight> list =new List<Flyweight>(); //从数据库里取出该用户的所有权限列表 foreach(var item in AuthorityList) { //然后到FlyweightFactory进行分离共享 Flyweight flyweight =FlyweightFactory.Instance().GetFlyweight(item.authority+","+item.item); list.add(flyweight); } dic[user]=list; } }
FlyweightFactory里的代码:
public class FlyweightFactory { private static FlyweightFactory factory=new FlyweightFactory(); private static FlyweightFactory Instance() { return factory; } //享元池 private Dictionary<string,Flyweight> fwDic=new Dictionary<string,Flyweight>(); //获取key对应的享元对象,保证相同类型的享元不会存在多个实例 public Flyweight GetFlyweight(string key) { Flyweight fly=fwDic.Get(key); if(fly==null) { //内部状态可以根据情况获取,这里内部状态就是通过逗号分隔的key获得 fly=new 权限Flyweight(内部状态); fwDic.add(key,fly); } return fly; } }
权限Flyweight里的代码:
public class 权限Flyweight { //内部状态 private string Authority; private string Item; public 权限Flyweight(string 内部状态) { string[] innerState=内部状态.split(","); Authority=innerState[0]; Item =innerState[1]; } public opertion(string 外部状态) { //外部状态可以通过方法传参的方式传递给享元对象进行使用 } }
这样一来就能减少本应大量存在于内存里的权限对象,假如现在已经有很多用户登录了,可以看看这时候内存里的对象情况:
可以看出Client里Dic的权限对象的引用实际上只指向FlyweightFactory里的fwDic的4个权限对象,如果不用享元模式的话,数据库里配置了多少权限就会有多少个权限对象(假如很多用户都登录了的话)
享元模式的本质:分离与共享