学习设计模式《十九》——享元模式
一、基础概念
享元模式的本质是【分离与共享】。
序号 | 说明 |
1 | 【分离】的是对象状态中变与不变的部分,【共享】的是对象中不变的部分; 享元模式的关键就在于【分离变与不变】把不变的部分作为享元对象的内部状态,而变化部分则作为外部状态,由外部来维护,这样享元对象就能够被共享,从而减少对象数量,并节省大量的内存空间。 |
2 | 在使用享元模式时,需要考虑【哪些状态需要分离?如何分离?分离后如何处理?哪些需要共享?如果管理共享的对象?外部如何使用共享的享元对象?是否需要不共享的对象?等】 |
享元模式的定义:运用共享技术有效地支持大量细粒度的对象。
序号 | 享元模式的结构 | 说明 |
1 | 享元接口 | 通过这个IFlyweight接口可以接收并作用于外部状态(即:通过这个接口传入外部状态,在享元对象的方法处理中可能会使用这些外部的数据)。 |
2 | 具体的享元对象 | 具体的享元对象必须是可共享的,需要封装IFlyweight接口的内部状态。 |
3 | 非共享的享元实现对象 | 非共享的享元实现对象通常是对共享享元对象的组合对象。 |
4 | 享元工厂 | 主要是用来创建并管理共享的享元对象,并对外提供访问共享享元的接口。 |
5 | 享元客户端 | 主要工作是维持一个对IFlyweight的引用,计算或存储享元对象的外部状态(可以访问共享和不共享的IFlyweight对象) |
序号 | 认识享元模式 | 说明 |
1 | 变与不变 | 享元模式设计的重点【在于分离变与不变】。把一个对象的状态分成【内部状态】和【外部状态】,内部状态是不变的,外部状态是可变的。然后通过共享不变的部分,达到减少对象数量并节约内存的目的。 1、在享元对象需要的时候,可以从外部传入外部状态给共享的对象,共享对象会在功能处理的时候,使用自己内部的状态和这些外部的状态。 |
2 | 共享与不共享 | 在享元模式中,享元对象又有共享和不共享之分;不共享的情况通常出现在与组合模式合用的情况,通常共享的是叶子对象,一般不共享的部分是由共享部分组合而成的(由于所有细粒度的叶子对象都已经缓存了,那么缓存组合对象就没有什么意义了) |
3 | 内部状态和外部状态 | 1、享元模式的内部状态通常指包含在享元对象内部的、对象本身的状态,是独立于使用享元的场景信息,一般创建后就不再变化的状态,因此可以共享。
|
4 | 实例池 | 在享元模式中,为了创建和管理共享的享元部分,引入了享元的工厂。享元工厂中一般都包含有享元对象的实例池,享元对象就是缓存在这个实例池中的。 所谓的实例池(指的是缓存和管理对象实例的程序,通常实例池会提供对象实例的运行环境,并控制对象实例的生命周期)。 工业级的实例池在实现上有两个最基本的难点:一个是动态控制实例数量,另一个是动态分配实例来提供给外部使用;这些都是需要算法来做保证的。 |
5 | 谁来初始化共享对象 | 在享元模式中,通常是在第一次向享元工厂请求获取共享对象的时候,进行共享对象的初始化,而且多半都是在享元工厂内部实现,不会从外部传入共享对象。 当然也可以从外部传入一些创建共享对象所需的值,享元工厂可以按照这些值去初始化需要共享的对象,然后把创建好的共享对象的实例放入享元工厂内部的缓存中,以后再请求这个共享对象的时候就不用再创建了。 |
序号 | 享元模式的优点 | 享元模式的缺点 |
1 | 减少对象数量,节省内存空间 1、可能有的朋友会认为共享对象会浪费空间,但是如果这些对象频繁使用,那么其实是节省空间的(因为占用空的的大小等于每个对象实例占用的大小再乘以数量,对于享元对象来讲,基本上就只有一个实例,大大减少了享元对象的数量,并节省了不少的内存空间)。 | 维护共享对象,需要额外开销 在维护共享对象的时候,如果功能复杂,会有很多额外的开销(如:需要创建一个线程来维护垃圾回收) |
何时选用享元模式?
《1》如果一个应用程序使用了大量的细粒度对象,可以使用享元模式来减少对象数量。
《2》如果由于使用大量的对象,造成很大的存储开销,可以使用享元模式来减少对象数量,并节约内存。
《3》如果对象的大多数状态都可以转变为外部状态(如:通过计算得到、从外部传入等)可以使用享元模式来实现内部状态和外部状态的分离。
《4》如果不考虑对象的外部状态,可以用相对较少的共享对象取代很多组合对象,可以使用享元模式来共享对象,然后组合对象来使用这些共享对象。
二、享元模式示例
业务需求:在应用程序中,普通人员可查看本部门人员列表的权限,但是在人员列表中每个人员的薪资数据,普通人员是不可以看到的;而部门经理在查看本部门人员列表的时候,就可以看到每个人员相应的薪资数据。现在我们就需要来实现为系统加入权限控制功能。
现在系统的授权工作已完成,数据记录在数据库中,记录了人员对安全实体所拥有的权限如下所示:
序号 | 人员名称 | 安全实体 | 权限 | 说明 |
1 | 张三 | 人员列表 | 查看 | 表示【张三】对【人员列表】拥有【查看】的权限 |
2 | 李四 | 人员列表 | 查看 | 表示【李四】对【人员列表】拥有【查看】的权限 |
3 | 李四 | 薪资数据 | 查看 | 表示【李四】对【薪资数据】拥有【查看】的权限 |
4 | 李四 | 薪资数据 | 修改 | 表示【李四】对【薪资数据】拥有【修改】的权限 |
由于操作人员进行授权操作后,各人员被授予的权限是记录在数据库中的,刚开始有开发人员提出,每次用户操作系统的时候,都直接到数据库中去动态查询,用以判断该人员是否拥有相应实体的权限。但很快被否决了【试想一下,用户操作那么频繁,每次都到数据库中动态查询,这会严重加剧数据库服务器的负担,使系统变慢】(为了加快系统运行的速度,开发小组决定采用一定的缓存。当每个人员登录的时候,就把该人员能操作的权限获取到,存储在内存中。这样每次操作的时候,就直接在内存中进行校验,速度会快很多,这是典型的空间换时间的做法) 。
权限系统的基础知识补充:(几乎所有的权限系统都分为两个部分【授权】【鉴权】):
1、【安全实体】是指被权限系统检测的对象(如:工资数据);
2、【权限】是指需要被校验的权限对象(如:查看、修改等);
3、【授权】是指把对某些安全实体的某些权限分配给某些人的过程。
4、【鉴权】是指判断某个任意对某个安全实体是否拥有某个或某些权限的过程。
安全实体和权限必须在一起才有意义(如:现在需要检测登录人员对工资数据是否有查看的权限,那么【工资数据】实体和【查看】权限一定要在一起才有意义;否则不在一起就不知道要做什么:
《1》如果只有安全实体没有权限就会变为(检测登录人员对工资数据;现在就不知道要对工资数据做什么,这样就不完整了)。
《2》如果只有权限就会变为(检测登录人员是否有查看权限;对谁有查看权限不清楚,也是不完整的))。
简单的说【授权过程】就是权限的分配过程,【鉴权】过程就是权限的匹配过程。在目前的应用系统中,大多数是利用数据库来存放授权过程产生的数据(即:授权就是向数据库添加数据或维护数据的过程)匹配过程就变成了从数据库中获取相应数据进行匹配的过程了。
在我们这里为了演示的简单性,就不再考虑权限的另外两个特征【继承性】【最近匹配原则】了:
1、【权限的继承性】是指如果多个安全实体存在包含关系,而某个安全实体没有相应的权限限制,那么它就会继承包含它的安全实体的相应权限(如:某个大楼和楼内的房间都是安全实体【即大楼这个安全实体包含楼内的房间实体】现在考虑一个具体的权限【进入某个房间的权限】,如果这个房间没有门,那么谁都可以进去【即:这个房间对应的实体就没有限制进入的权限】,那么是不是可以说所有人都可以进入这个房间呢?很显然不是,因为某人要进入这个房间的前提是能够进入这座大楼【也就是说:此时这个房间实体虽然没有进入的权限限制,但是它会继承父级大楼的进入权限】)。
2、【权限的最近匹配原则】是指如果多个安全实体存在包含关系,而某个安全实体没有相应的权限限制,那么它会向上寻找并匹配相应的权限限制,直到找到一个离这个安全实体最近的拥有相应权限限制的安全实体为止。如果把整个层次结构都寻找完了仍然没有匹配到相应的权限限制,那就说明所有人都对这个安全实体都拥有这个相应的权限限制(即:这个大楼坐落在某个园区内,要进入某个房间,首先要有进入这个大楼的权限,要进入大楼还需要有能够进入园区的权限)。
2.1、不用模式的示例
2.1.1、定义授权数据对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.NoPattern
{/// <summary>/// 授权数据的模型/// </summary>internal class AuthorizationModel{/// <summary>/// 人员/// </summary>public string? User { get; set; }/// <summary>/// 安全实体/// </summary>public string? SecurityEntity { get; set; }/// <summary>/// 权限/// </summary>public string? Permit { get; set; }public override string ToString(){string str = $"【{User}】对【{SecurityEntity}】拥有【{Permit}】权限";return str;}}//Class_end
}
2.1.2、实现一个内存数据库存储授权数据
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.NoPattern
{/// <summary>/// 在内存中模拟存储在数据库中的值/// </summary>internal class TestDB{//用来存放授权数据的值public List<string> colDB=new List<string>();public TestDB(){FillDatas();}private void FillDatas(){AddInfoToList(ref colDB,"张三,人员列表,查看");AddInfoToList(ref colDB,"李四,人员列表,查看");AddInfoToList(ref colDB,"李四,薪资数据,查看");AddInfoToList(ref colDB,"李四,薪资数据,修改");for (int i = 0; i < 3; i++){AddInfoToList(ref colDB, $"张三{i},人员列表,查看");}}private void AddInfoToList<T>(ref List<T> list,T needAddInfo){if (list==null){list = new List<T>();}if (!list.Contains(needAddInfo) && (needAddInfo != null || !needAddInfo.Equals(""))){list.Add(needAddInfo);}}}//Class_end
}
2.1.3、实现鉴权业务
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.NoPattern
{/// <summary>/// 安全管理【实现为单例】/// </summary>internal class SecurityMgr{private static readonly Lazy<SecurityMgr> lazy=new Lazy<SecurityMgr>(() => new SecurityMgr());//禁止该类被外部newprivate SecurityMgr(){}//本类实例public static SecurityMgr Instance { get {/*Console.WriteLine($"SecurityMgr单例类的编号是{lazy.Value.GetHashCode()}");*/ return lazy.Value; } }//获取数据库人员信息(内存模拟数据)TestDB testDB= new TestDB();//运行期间用来存放登录人员的对应权限private Dictionary<string, List<AuthorizationModel>> personInfoDic = new Dictionary<string, List<AuthorizationModel>>();/// <summary>/// 模拟登录功能/// </summary>/// <param name="user">登录的用户</param>public void Login(string user){//登录时就需要把该用户所拥有的权限从数据库中获取出来List<AuthorizationModel> col = QueryByUser(user);//然后将从数据库获取的用户数据放到缓存中AddInfoToDic(ref personInfoDic,user,col);}/// <summary>/// 判断某个用户对某个安全实体是否拥有某种权限/// </summary>/// <param name="user">被检测权限的用户</param>/// <param name="securityEntity">安全实体</param>/// <param name="permit">权限</param>/// <returns>true:表示【用户】拥有【安全实体】的【权限】</returns>public bool HasPermit(string user,string securityEntity,string permit){Console.WriteLine($"现在开始判断【{user}】是否拥有【{securityEntity}】模块的【{permit}】权限 ");bool res= false;if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(securityEntity) || string.IsNullOrEmpty(permit)){return false;}List<AuthorizationModel> col = personInfoDic[user];if (col==null || col.Count<=0){Console.WriteLine($"【{user}】没有登录或是被分配任务的权限");return false;}foreach (var item in col){Console.WriteLine($"查询到【{item}】该内容对应的唯一编号是【{item.GetHashCode()}】");if (item.SecurityEntity.Equals(securityEntity)&&item.Permit.Equals(permit)){return true;}}return res;}//从数据库中获取某人所拥有的权限private List<AuthorizationModel> QueryByUser(string user){List<AuthorizationModel> col = new List<AuthorizationModel>();if (!string.IsNullOrEmpty(user)){foreach (var item in testDB.colDB){string[] strArray = item.Split(',');if (strArray[0].Equals(user)){AuthorizationModel am = new AuthorizationModel();am.User= strArray[0];am.SecurityEntity = strArray[1];am.Permit= strArray[2];col.Add(am);}}}return col;}//添加信息到字典中private void AddInfoToDic(ref Dictionary<string,List<AuthorizationModel>> personInfoDic, string user,List<AuthorizationModel> authorizations){if (string.IsNullOrEmpty(user) || authorizations==null || authorizations.Count<=0){return;}if (personInfoDic.ContainsKey(user)){personInfoDic[user] = authorizations;}else{personInfoDic.Add(user, authorizations);}}}//Class_end
}
2.1.4、客户端测试
namespace FlyweightPattern
{internal class Program{static void Main(string[] args){TestNoPattern();Console.ReadLine();}/// <summary>/// 不使用模式的测试/// </summary>private static void TestNoPattern(){Console.WriteLine("------不使用模式的测试------");//先登录,然后判断是否有权限NoPattern.SecurityMgr.Instance.Login("张三");NoPattern.SecurityMgr.Instance.Login("李四");bool f1 = NoPattern.SecurityMgr.Instance.HasPermit("张三","薪资数据","查看");Console.WriteLine($"【张三】对【薪资数据】拥有【查看】权限 结果【{f1}】\n");bool f2 = NoPattern.SecurityMgr.Instance.HasPermit("李四","薪资数据","查看");Console.WriteLine($"【李四】对【薪资数据】拥有【查看】权限 结果【{f2}】\n");//然后检查是否具有其他内容的for (int i = 0; i < 3; i++){string user = $"张三{i}";NoPattern.SecurityMgr.Instance.Login(user);bool res = NoPattern.SecurityMgr.Instance.HasPermit($"{user}","人员列表","查看");Console.WriteLine($"【{user}】对【人员列表】拥有【查看】权限结果是【{res}】\n");}}}//Class_end
}
2.1.5、运行结果
以上不使用模式的示例已经实现了对指定用户的鉴权操作(且考虑了性能问题,在内存中缓存了每个人的权限数据,这样就使得每次判断权限的时候,速度提升明显)。
虽然以上不使用模式的示例已经实现了对指定用户的鉴权操作。但是我们仔细观察一下还存在如下几个问题:
《1》缓存时间长度的问题(这些缓存的数据应该被缓存多久?如果是Web应用程序,这种与登录人员相关的权限数据,大多数是放在session中进行缓存,当session超时时,就会被清理掉。如果不是Web应用呢?那就需要自己来控制了,另外就算是在Web应用中,也不一定非要缓存到seesion超时才清理)【总之控制缓存数据应该被缓存多长时间,是实现高效缓存的一个问题点】。
《2》缓存数据和真实数据的同步问题(这里的同步不是线程的同步;而是指授权的数据存在数据库中,运行时缓存到内存中,如果真实的授权在运行期间发生改变,那么缓存中的数据就应该和数据库中的数据同步,以保持一致,否则数据就出错了)【如何合理的同步数据,也是实现高效缓存的一个问题点】。
《3》缓存的多线程并发控制(对于缓存的数据,有些操作从缓存中取值,有些操作向缓存中添加值,有些操作在清理过期的缓存数据,有些操作在进行缓存和真实数据的同步。在一个多线程的环境下,如何合理地对缓存进行并发控制,也是实现高效缓存的一个问题点)。
还有就是观察输出内容最后的唯一编号是实例的HashCode,都是不同的,也就是说这些对象实例不是同一个对象实例【也就是说:对象实例数据太多】(目前是一条数据就有一个对象实例,数据库的数据量是很大的,如果有几万、几十万条数据,按照上面的实现就会有几万、几十万的对象实例,这样会耗费大量的内存)。另外,这些对象的粒度很小,都是简单地描述某一个方面的对象,而且很多数据是重复的,在这些大量重复的数据上耗费了很多内存。
对与【安全实体】和【权限】一般要联合描述,因此对于【人员列表】这个安全实体的【查看】权限就算是授权给不同的人员,这个描述都是一样的(假设在某极端情况下,要把【人员列表】这个安全实体的【查看】权限授权给一万个人,那么数据库中将会有一万条数据记录,按照不使用模式的方式会有一万个对象实例,且这些实例的大部分数据都是重复的,切回重复一万次,这就是一个很大的问题了)。总结起来就是【当前实现的功能,存在大量细粒度的对象,而且存在大量的重复数据,严重耗费内存】。
2.2、使用享元模式的示例1——共享享元
现在造成内存浪费的主要原因是【细粒度对象太多】且【有大量重复的数据】,如果能够有效地减少对象的数量,减少重复,那么就能节省不少内存了:
一个基本的思路是缓存这些包含着重复数据的对象,让这些对象只出现一次,那么就只耗费一份内存了。注意:并不是所有对象都适合缓存,因为缓存的是对象的实例,实例里面存放的主要是对象的属性值。因此,如果被缓存的对象的属性值经常变动,那就不适合缓存了【这是因为真实对象属性值变化了,那么缓存中的对象也必须跟着变化,否则缓存中的数据就跟真实对象的数据不同步,就是错误数据了】。
因此,我们需要分离出被缓存对象的实例中,哪些数据是不变且重复出现的,哪些数据是经常变化的,真正应该被缓存的数据是那些不变且重复出现的数据,把他们成为对象的内部状态;而那些变化的数据就不缓存了,把它们称为外部状态。这样在实现的时候,把内部状态分离出来共享【称之为享元】通过共享享元对象来减少对内存的占用。把外部状态分离出来,放到外部,让应用在使用的时候进行维护,并在需要的时候传递给享元对象使用。为了控制对内部状态的共享,并且让外部能够简单地使用共享数据,提供一个工厂来管理享元【称之为享元工厂】。
我们经过分析可以发现:重复出现的数据主要是【安全实体】和【权限】的描述,又考虑到安全实体和权限是不分开的,那么我们可以将安全实体和权限看作是一个整体状态【即:将安全实体和权限的描述定义为享元】而和享元结合的人员名称就可以看作是享元的外部数据。
2.2.1、定义享元接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoOne
{/// <summary>/// 享元接口【通过这个接口可以接受并作用于外部状态】/// </summary>internal interface IFlyweight{/// <summary>/// 判断传入的安全实体和权限是否与享元对象内部状态匹配/// </summary>/// <param name="securityEntity">安全实体</param>/// <param name="permit">权限</param>/// <returns>true:表示匹配</returns>bool Math(string securityEntity,string permit);}//Interface_end
}
2.2.2、实现具体的享元对象
享元对象封装授权数据中重复出现的数据且这些数据不经常变动:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoOne
{/// <summary>/// 封装授权数据库中重复出现的享元对象/// </summary>internal class AuthorizationFlyweight : IFlyweight{//内部安全实体private string securityEntity = string.Empty;//内部权限private string permit=string.Empty;/// <summary>/// 安全实体可供外部访问,但不让外部修改/// </summary>public string SecurityEntity { get => securityEntity;}/// <summary>/// 权限可供外部访问,但不让外部修改/// </summary>public string Permit { get => permit;}/// <summary>/// 构造函数/// </summary>/// <param name="state">状态数据【包含安全实体和权限用逗号分隔】</param>public AuthorizationFlyweight(string state){if (!string.IsNullOrEmpty(state)){if (state.Contains(',')){string[] strArray = state.Split(',');securityEntity = strArray[0];permit = strArray[1];}}}/// <summary>/// 匹配实体和权限/// </summary>/// <param name="securityEntity"></param>/// <param name="permit"></param>/// <returns></returns>public bool Math(string securityEntity, string permit){if (string.IsNullOrEmpty(securityEntity) || string.IsNullOrEmpty(permit)){return false;}if (this.securityEntity.Equals(securityEntity) && this.permit.Equals(permit)){return true;}return false;}public override string ToString(){string str = $"【{SecurityEntity}】拥有【{Permit}】权限";return str;}}//Class_end
}
2.2.3、实现享元工厂管理享元对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoOne
{/// <summary>/// 享元工厂【实现为单例】/// </summary>internal class FlyweightFactory{//本类实例private static readonly Lazy<FlyweightFactory> lazy = new Lazy<FlyweightFactory>(() => new FlyweightFactory());//本类单例public static FlyweightFactory Instance { get { return lazy.Value; } }//禁止该类被外部newprivate FlyweightFactory(){}//缓存多个Flyweight对象private Dictionary<string,IFlyweight> flyweightDic= new Dictionary<string,IFlyweight>();//获取state对应的享元对象public IFlyweight GetIFlyweight(string state){IFlyweight fw= null;if (string.IsNullOrEmpty(state)) return fw;if (flyweightDic.ContainsKey(state)){fw = flyweightDic[state];}else{fw = new AuthorizationFlyweight(state);AddInfoToDic(state,fw);}return fw;}//添加信息到字典中private void AddInfoToDic(string needAddKey,IFlyweight flyweight){if (string.IsNullOrEmpty(needAddKey) || flyweight==null){return;}if (flyweightDic != null && flyweightDic.ContainsKey(needAddKey)){flyweightDic.Add(needAddKey, flyweight);}else{flyweightDic[needAddKey]= flyweight;}}}//Class_end
}
2.2.4、实现内存数据库存储授权数据
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoOne
{/// <summary>/// 在内存中模拟存储在数据库中的值/// </summary>internal class TestDB{//用来存放授权数据的值public List<string> colDB = new List<string>();public TestDB(){FillDatas();}private void FillDatas(){AddInfoToList(ref colDB, "张三,人员列表,查看");AddInfoToList(ref colDB, "李四,人员列表,查看");AddInfoToList(ref colDB, "李四,薪资数据,查看");AddInfoToList(ref colDB, "李四,薪资数据,修改");for (int i = 0; i < 3; i++){AddInfoToList(ref colDB, $"张三{i},人员列表,查看");}}private void AddInfoToList<T>(ref List<T> list, T needAddInfo){if (list == null){list = new List<T>();}if (!list.Contains(needAddInfo) && (needAddInfo != null || !needAddInfo.Equals(""))){list.Add(needAddInfo);}}}//Class_end
}
2.2.5、实现安全管理类
既然已经实现了对享元对象的管理,那么在这里就可以使用这些享元对象进行业务功能的处理。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoOne
{/// <summary>/// 安全管理【实现为单例】/// </summary>internal class SecurityMgr{//本类实例private static readonly Lazy<SecurityMgr> lazy= new Lazy<SecurityMgr>(() => new SecurityMgr());//本类单例public static SecurityMgr Instance { get { return lazy.Value; } }//禁止该类被外部newprivate SecurityMgr(){}//获取数据库人员信息TestDB testDB= new TestDB();//在运行期间用来存放登录人员对应的权限【web应用中,这些人员对应的权限数据通常会存放到session中】private Dictionary<string, List<IFlyweight>> personInfoDic = new Dictionary<string, List<IFlyweight>>();//模拟登录public void Login(string user){//登录时就把该用户所拥有的权限内容从数据库中提取出来List<IFlyweight> col = QueryByUser(user);//将从数据库提取的数据存放到缓存中AddInfoToDic(user,col);}//判断某个用户对某个安全实体是否拥有某种权限public bool HasPermit(string user,string securityEntity,string permit){if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(securityEntity)||string.IsNullOrEmpty(permit)){return false;}List<IFlyweight> col = personInfoDic[user];if (col==null && col?.Count==0){Console.WriteLine($"【{user}】没有登录或是没有被分配任务权限");return false;}foreach (var item in col){//输出当前的实力,看看是否为同一个实例对象Console.WriteLine($"查询到【{item}】 该内容对应的唯一编号是【{item.GetHashCode()}】");if (item.Math(securityEntity,permit)){return true;}}return false;}//从数据库中获取某人所拥有的权限private List<IFlyweight> QueryByUser(string user){List<IFlyweight> col=new List<IFlyweight>();if (!string.IsNullOrEmpty(user)){foreach (var item in testDB.colDB){string[] strArray = item.Split(',');if (strArray[0].Equals(user)){string state = $"{strArray[1]},{strArray[2]}";IFlyweight fw = FlyweightFactory.Instance.GetIFlyweight(state);col.Add(fw);}}}return col;}//添加信息到字典中private void AddInfoToDic(string user, List<IFlyweight> authorizationList){if (string.IsNullOrEmpty(user) || authorizationList == null || authorizationList.Count<=0){return;}if (personInfoDic != null && personInfoDic.ContainsKey(user)){personInfoDic[user] = authorizationList;}else{personInfoDic.Add(user,authorizationList);}}}//Class_end
}
2.2.6、客户端测试
namespace FlyweightPattern
{internal class Program{static void Main(string[] args){TestFlyweightDemoOne();Console.ReadLine();}/// <summary>/// 测试享元模式示例一/// </summary>private static void TestFlyweightDemoOne(){Console.WriteLine("------测试享元模式示例一------");FlyweightDemoOne.SecurityMgr securityMgr = FlyweightDemoOne.SecurityMgr.Instance;securityMgr.Login("张三");securityMgr.Login("李四");bool f1 = securityMgr.HasPermit("张三","薪资数据","查看");Console.WriteLine($"【张三】对【薪资数据】拥有【查看】权限 结果【{f1}】\n");bool f2 = securityMgr.HasPermit("李四","薪资数据","查看");Console.WriteLine($"【李四】对【薪资数据】拥有【查看】权限 结果【{f2}】\n");//然后检查是否具有其他内容的for (int i = 0; i < 3; i++){string user = $"张三{i}";securityMgr.Login(user);bool res = securityMgr.HasPermit($"{user}", "人员列表", "查看");Console.WriteLine($"【{user}】对【人员列表】拥有【查看】权限结果是【{res}】\n");}}}//Class_end
}
2.2.7、运行结果
我们观察输出的结果可以看到有6行的唯一编号数据中,有5条的HashCode都是同一个值,根据我们的实现,可以断定这个5个相同HashCode的实例是同一个对象。也就是目前只有2个对象实例。相比不使用模式的示例6个对象实例,我们这里已经成功实现对象实例数量的减少。
在这个享元模式的示例中,我们封装了【安全实体】和【权限】的细粒度对象,即是授权分配的单元对象,也是鉴权的单元对象;通过封装【安全实体】和【权限】的细粒度对象,无论多少人拥有这个权限,实际的对象实例都是只有一个,这样即减少了对象的数目,又节省了宝贵的内存空间。这样就解决了本节开始提出的【细粒度对象太多】且【有大量重复的数据】问题。
2.3、使用享元模式的示例2——不共享享元
不共享享元的情况多出现在组合结构中,对于使用已经缓存的享元组合出来的对象,就没有必要在缓存了(也就是把已经缓存的享元当做叶子节点,组合出来的组合对象就不再需要缓存)【把这种享元称为复合享元】(如:要给某人分配【薪资数据】这个安全实体的【修改】权限,那么一定会把【薪资数据】安全实体和【查看】权限也分配给这个人。如果按照上面的享元做法,就需要分配两个对象,为了方便,干脆把这两个描述组合起来,打包为一个对象,命名为【操作薪资数据】:
那么分配权限时可以这样描述(把【操作薪资数据】分配给【张三】),这个含义也就包含:
1、(把【薪资数据】的【查看】权限分配给【张三】);
2、(把【薪资数据】的【修改】权限分配给【张三】))。
这样一来,【操作薪资数据】就相当于是一个不需要的共享享元,它实际是由以上2个享元组合而成。且这样分配权限的时候也会更加简单一点。但是这种组合对象在权限系统中一般不用于验证【即:鉴权的时候还是一个一个的进行判断,因为在存储授权信息的时候是一条条存储的】。
2.3.1、定义享元接口
这里的享元接口除了原有的享元方法外,还要添加对组合对象的操作方法(即:主要是向组合对象中加入子对象方法):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoTwo
{/// <summary>/// 享元接口【通过这个接口可以接受并作用于外部状态】/// </summary>internal interface IFlyweight{/// <summary>/// 判断传入的安全实体和权限是否与享元对象内部状态匹配/// </summary>/// <param name="securityEntity">安全实体</param>/// <param name="permit">权限</param>/// <returns>true:表示匹配</returns>bool Math(string securityEntity, string permit);/// <summary>/// 为享元接口添加子享元对象/// </summary>/// <param name="fw">被添加的子享元对象</param>void Add(IFlyweight fw);}//Interface_end
}
2.3.2、实现具体的享元对象
由于这个添加方法是针对组合对象的,因此在叶子对象这里抛出不支持的例外就可以了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoTwo
{/// <summary>/// 封装授权数据库中重复出现的享元对象/// </summary>internal class AuthorizationFlyweight : IFlyweight{//内部安全实体private string securityEntity = string.Empty;//内部权限private string permit = string.Empty;/// <summary>/// 安全实体可供外部访问,但不让外部修改/// </summary>public string SecurityEntity { get => securityEntity; }/// <summary>/// 权限可供外部访问,但不让外部修改/// </summary>public string Permit { get => permit; }/// <summary>/// 构造函数/// </summary>/// <param name="state">状态数据【包含安全实体和权限用逗号分隔】</param>public AuthorizationFlyweight(string state){if (!string.IsNullOrEmpty(state)){if (state.Contains(',')){string[] strArray = state.Split(',');securityEntity = strArray[0];permit = strArray[1];}}}/// <summary>/// 匹配实体和权限/// </summary>/// <param name="securityEntity"></param>/// <param name="permit"></param>/// <returns></returns>public bool Math(string securityEntity, string permit){if (string.IsNullOrEmpty(securityEntity) || string.IsNullOrEmpty(permit)){return false;}if (this.securityEntity.Equals(securityEntity) && this.permit.Equals(permit)){return true;}return false;}public override string ToString(){string str = $"【{SecurityEntity}】拥有【{Permit}】权限";return str;}public void Add(IFlyweight fw){throw new Exception("对象不支持这个功能");}}//Class_end
}
2.3.3、实现不需要享元的享元对象
这里不需要享元的享元对象其实就是组合共享享元对象的对象,在这个组合中,需要保存所有的子对象,另外它在实现匹配方法的时候,是通过递归的方式匹配的。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoTwo
{/// <summary>/// 不需要共享的享元对象实现【即组合模式中的组合对象】/// </summary>internal class UnsharedConcreteFlyweight:IFlyweight{//记录每个组合对象所包含的子组件private List<IFlyweight> flyweightlist = new List<IFlyweight>();public void Add(IFlyweight fw){flyweightlist.Add(fw);}public bool Math(string securityEntity, string permit){foreach (var fw in flyweightlist){//递归调用if (fw.Math(securityEntity,permit)){return true;}}return false;}public override string ToString(){string str = "";if (flyweightlist!=null && flyweightlist?.Count>0){foreach (var fw in flyweightlist){str+= $"{fw.ToString()}";}}return str;}}//Class_end
}
2.3.4、实现内存数据库存储的授权数据
这里的实现相比前面的变动点为:
1、需要区分是单条授权还是组合授权【即:在每条授权数据后面添加一个标识来描述】;
2、增加一个描述组合数据的记录,使用字典来存放。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoTwo
{/// <summary>/// 在内存中模拟存储在数据库中的值/// </summary>internal class TestDB{//用来存放单独授权数据的值public List<string> colDB = new List<string>();//用来存放组合授权数据的值public Dictionary<string, string[]>dicDB = new Dictionary<string, string[]>();public TestDB(){FillDatas();}public void FillDatas(){AddInfoToList(ref colDB, "张三,人员列表,查看,1");AddInfoToList(ref colDB, "李四,人员列表,查看,1");AddInfoToList(ref colDB, "李四,操作薪资数据,,2");dicDB.Add("操作薪资数据",new string[] {"薪资数据,查看","薪资数据,修改"});//增加更多的授权for (int i = 0; i < 3; i++){AddInfoToList(ref colDB, $"张三{i},人员列表,查看,1");}}private void AddInfoToList<T>(ref List<T> list, T needAddInfo){if (list == null){list = new List<T>();}if (!list.Contains(needAddInfo) && (needAddInfo != null || !needAddInfo.Equals(""))){list.Add(needAddInfo);}}}//Class_end
}
2.3.5、实现享元工厂管理享元对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoTwo
{/// <summary>/// 享元工厂【实现为单例】/// </summary>internal class FlyweightFactory{//本类实例private static readonly Lazy<FlyweightFactory> lazy = new Lazy<FlyweightFactory>(() => new FlyweightFactory());//本类单例public static FlyweightFactory Instance { get { return lazy.Value; } }//禁止本类被外部newprivate FlyweightFactory(){}//缓存多个Flyweight对象private Dictionary<string, IFlyweight> flyweightDic = new Dictionary<string, IFlyweight>();//获取state对应的享元对象public IFlyweight GetIFlyweight(string state){IFlyweight fw = null;if (string.IsNullOrEmpty(state)) return fw;if (flyweightDic.ContainsKey(state)){fw = flyweightDic[state];}else{fw = new AuthorizationFlyweight(state);AddInfoToDic(state, fw);}return fw;}//添加信息到字典中private void AddInfoToDic(string needAddKey, IFlyweight flyweight){if (string.IsNullOrEmpty(needAddKey) || flyweight == null){return;}if (flyweightDic != null && flyweightDic.ContainsKey(needAddKey)){flyweightDic.Add(needAddKey, flyweight);}else{flyweightDic[needAddKey] = flyweight;}}}//Class_end
}
2.3.6、实现安全管理类
在这个安全管理类中,不仅会使用享元对象,还会使用不需要共享的享元对象:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoTwo
{/// <summary>/// 安全管理【实现为单例】/// </summary>internal class SecurityMgr{//本类示例private static readonly Lazy<SecurityMgr> lazy= new Lazy<SecurityMgr>(() => new SecurityMgr());//本类单例public static SecurityMgr Instance { get { return lazy.Value; } }//禁止该类被外部newprivate SecurityMgr(){ }//获取数据库人员信息TestDB testDB = new TestDB();//运行期间,用来存放登录人员对应的权限【在Web应用中,这些数据通常存放在session中】private Dictionary<string, List<IFlyweight>> personInfoDic = new Dictionary<string, List<IFlyweight>>();//模拟登录public void Login(string user){//登录时就把该用户所拥有的权限从数据库中提取出来List<IFlyweight> col=QueryByUser(user);//将从数据库中提取的该用户权限内容都放到缓存中去AddInfoToDic(user, col);}//判断某用户对某个安全实体是否拥有某种权限public bool HasPermit(string user,string securityEntity,string permit){if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(securityEntity) ||string.IsNullOrEmpty(permit)){return false;}List<IFlyweight> col = personInfoDic[user];if (col == null && col?.Count == 0){Console.WriteLine($"【{user}】没有登录或是没有被分配任何权限");return false;}foreach (var item in col){//输出当前的实例,看看是否为同一个实例对象Console.WriteLine($"查询到【{item}】 该内容对应的唯一编号是【{item.GetHashCode()}】容器数量是【{personInfoDic?.Count}】");if (item.Math(securityEntity, permit)){return true;}}return false;}//从数据库中获取某人所拥有的权限private List<IFlyweight> QueryByUser(string user){List<IFlyweight> col = new List<IFlyweight>();if (!string.IsNullOrEmpty(user)){foreach (var item in testDB.colDB){string[] strArray = item.Split(',');if (strArray[0].Equals(user)){IFlyweight fw = null;//表示权限的组合(不共享的享元)if (strArray[3].Equals("2")){fw = new UnsharedConcreteFlyweight();//获取需要组合的数据string combinationName = strArray[1];string[] strCombination = testDB.dicDB[combinationName];foreach (string combination in strCombination){IFlyweight fwtmp = FlyweightFactory.Instance.GetIFlyweight(combination);//把这个对象加入到组合对象中fw.Add(fwtmp);}}else{//表示单个享元string state = $"{strArray[1]},{strArray[2]}";fw = FlyweightFactory.Instance.GetIFlyweight(state);}col.Add(fw);}}}return col;}//添加信息到字典中private void AddInfoToDic(string user, List<IFlyweight> authorizationList){if (string.IsNullOrEmpty(user) || authorizationList == null || authorizationList.Count <= 0){return;}if (personInfoDic != null && personInfoDic.ContainsKey(user)){personInfoDic[user] = authorizationList;}else{personInfoDic.Add(user, authorizationList);}}}//Class_end
}
2.3.7、客户端测试
namespace FlyweightPattern
{internal class Program{static void Main(string[] args){TestFlyweightDemoTwo();Console.ReadLine();}/// <summary>/// 测试享元模式示例二/// </summary>private static void TestFlyweightDemoTwo(){Console.WriteLine("------测试享元模式示例二------");//先登录,然后在判断是否有权限FlyweightDemoTwo.SecurityMgr securityMgr= FlyweightDemoTwo.SecurityMgr.Instance;securityMgr.Login("张三");securityMgr.Login("李四");bool f1 = securityMgr.HasPermit("张三","薪资数据","查看");Console.WriteLine($"【张三】对【薪资数据】拥有【查看】权限 结果【{f1}】\n");bool f2 = securityMgr.HasPermit("李四","薪资数据","查看");Console.WriteLine($"【李四】对【薪资数据】拥有【查看】权限 结果【{f2}】\n");bool f3 = securityMgr.HasPermit("李四", "薪资数据", "修改");Console.WriteLine($"【李四】对【薪资数据】拥有【修改】权限 结果【{f3}】\n");//然后检查是否具有其他内容的for (int i = 0; i < 3; i++){string user = $"张三{i}";securityMgr.Login(user);bool res = securityMgr.HasPermit($"{user}", "人员列表", "查看");Console.WriteLine($"【{user}】对【人员列表】拥有【查看】权限结果是【{res}】\n");}}}//Class_end
}
2.3.8、运行结果
2.4、使用享元模式的示例3
在前面的示例中,共享的享元对象是很多人共享的,基本上可以一直存在于系统中,不用清除。但是垃圾清除是享元对象管理的一个常见功能。
1、【垃圾】是指在缓存中存在,但是不需要被使用的缓存对象;【垃圾清除】是指在不需要这些数据的时候,应该把这些数据从缓存中清除,释放相应的内存空间,以节约资源;要实现垃圾回收首先要确定哪些是垃圾?其次是何时回收?最后就是有谁来回收?如何回收?
《1》如何确定哪些是垃圾(一个简单的方案是定义一个缓存对象的配置对象,在这个对象中描述了缓存的开始时间和最长不被使用的时间,这个时候判断是否垃圾的计算公式是【当前时间-缓存的开始时间>=最长不被使用的时间】;每次这个对象被使用的时候,就把缓存开始的时间更新为使用时的当前时间,如果一直有人用的话,这个对象就不会被判断为垃圾)。
《2》何时回收(当然是判断出是垃圾的时候就可以回收了)。
《3》关键是谁来判断垃圾,还有谁来回收垃圾(一个简单的方案是定义一个内部线程,这个线程在享元工厂被创建的时候就启动运行,由于这个线程每隔一定的时间来循环缓存中所有对象的缓存配置,看看是否是垃圾,如果是垃圾,那就可以启动回收了)。
《4》怎么回收(就是直接从缓存的字典对象中删除对应的对象,让这些对象没有引用的地方,那么这些对象就可以等着被CG的垃圾回收了)。
2、【引用计数】是指享元工厂能够记录每个享元被使用的次数;要实现引用计数,就在享元工厂中定义一个字典,字典的key值与缓存享元对象的key是一样的,而value就是被引用的次数,这样当外部每次获取该享元的时候,就把对应的引用计数取出来加上1,然后在记录回去。
2.4.1、定义享元接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 享元接口【通过这个接口可以接受并作用于外部状态】/// </summary>internal interface IFlyweight{/// <summary>/// 判断传入的安全实体和权限是否与享元对象内部状态匹配/// </summary>/// <param name="securityEntity">安全实体</param>/// <param name="permit">权限</param>/// <returns>true:表示匹配</returns>bool Math(string securityEntity, string permit);/// <summary>/// 为享元接口添加子享元对象/// </summary>/// <param name="fw">被添加的子享元对象</param>void Add(IFlyweight fw);}//Interface_end
}
2.4.2、实现具体的享元对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 封装授权数据库中重复出现的享元对象/// </summary>internal class AuthorizationFlyweight : IFlyweight{//内部安全实体private string securityEntity = string.Empty;//内部权限private string permit = string.Empty;/// <summary>/// 安全实体可供外部访问,但不让外部修改/// </summary>public string SecurityEntity { get => securityEntity; }/// <summary>/// 权限可供外部访问,但不让外部修改/// </summary>public string Permit { get => permit; }/// <summary>/// 构造函数/// </summary>/// <param name="state">状态数据【包含安全实体和权限用逗号分隔】</param>public AuthorizationFlyweight(string state){if (!string.IsNullOrEmpty(state)){if (state.Contains(',')){string[] strArray = state.Split(',');securityEntity = strArray[0];permit = strArray[1];}}}/// <summary>/// 匹配实体和权限/// </summary>/// <param name="securityEntity"></param>/// <param name="permit"></param>/// <returns></returns>public bool Math(string securityEntity, string permit){if (string.IsNullOrEmpty(securityEntity) || string.IsNullOrEmpty(permit)){return false;}if (this.securityEntity.Equals(securityEntity) && this.permit.Equals(permit)){return true;}return false;}public override string ToString(){string str = $"【{SecurityEntity}】拥有【{Permit}】权限";return str;}public void Add(IFlyweight fw){throw new Exception("对象不支持这个功能");}}//Class_end
}
2.4.3、实现不需要享元的享元对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 不需要共享的享元对象实现【即组合模式中的组合对象】/// </summary>internal class UnsharedConcreteFlyweight : IFlyweight{//记录每个组合对象所包含的子组件private List<IFlyweight> flyweightlist = new List<IFlyweight>();public void Add(IFlyweight fw){flyweightlist.Add(fw);}public bool Math(string securityEntity, string permit){foreach (var fw in flyweightlist){//递归调用if (fw.Math(securityEntity, permit)){return true;}}return false;}public override string ToString(){string str = "";if (flyweightlist != null && flyweightlist?.Count > 0){foreach (var fw in flyweightlist){str += $"{fw.ToString()}";}}return str;}}//Class_end
}
2.4.4、实现内存数据库存储的授权数据
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 在内存中模拟存储在数据库中的值/// </summary>internal class TestDB{//用来存放单独授权数据的值public List<string> colDB = new List<string>();//用来存放组合授权数据的值public Dictionary<string, string[]> dicDB = new Dictionary<string, string[]>();public TestDB(){FillDatas();}public void FillDatas(){AddInfoToList(ref colDB, "张三,人员列表,查看,1");AddInfoToList(ref colDB, "李四,人员列表,查看,1");AddInfoToList(ref colDB, "李四,操作薪资数据,,2");dicDB.Add("操作薪资数据", new string[] { "薪资数据,查看", "薪资数据,修改" });//增加更多的授权for (int i = 0; i < 3; i++){AddInfoToList(ref colDB, $"张三{i},人员列表,查看,1");}}private void AddInfoToList<T>(ref List<T> list, T needAddInfo){if (list == null){list = new List<T>();}if (!list.Contains(needAddInfo) && (needAddInfo != null || !needAddInfo.Equals(""))){list.Add(needAddInfo);}}}//Class_end
}
2.4.5、实现缓存配置文件模型对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 缓存配置文件模型对象/// </summary>internal class CacheConfModel{/// <summary>/// 缓存开始计时的开始时间/// </summary>public long BeginTime { get; set; } = 0;/// <summary>/// 缓存对象存放的持续时间【即:最长不被使用的时间】/// </summary>public long DurableTime { get; set; }=0;/// <summary>/// 缓存对象需要被永久存储【不需要从缓存中删除】/// </summary>public bool Forever { get; set; }=false;}//Class_end
}
2.4.6、实现享元工厂管理享元对象
在原有享元工厂的基础上进行改进,改变的内容如下:
《1》添加一个字典,用来缓存被共享对象的缓存配置数据。
《2》添加一个字典,用来记录缓存对象被引用的次数(为了测试方便,定义了一个常量来描述缓存的持续时间)。
《3》提供某个享元被使用的次数方法。
《4》在获取享元的对象中,就要设置相应的引用计数和缓存设置(我们这里采用内部默认设置一个缓存设置。也可以改造一下获取享元的方法,从外部传入缓存设置的数据)。
《5》提供一个清除缓存的线程,实现判断缓存数据是否已经是垃圾(如果是,那就把它从缓存中清除掉)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading.Tasks;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 享元工厂【实现为单例】实现垃圾回收和引用计数功能/// </summary>internal class FlyweightFactory{//本类实例private static readonly Lazy<FlyweightFactory> lazy= new Lazy<FlyweightFactory>(() => new FlyweightFactory());//本类单例public static FlyweightFactory Instance { get { return lazy.Value; } }//禁止本类被外部newprivate FlyweightFactory() {//启动清除缓存值的线程Task task = Task.Run(() =>{ClearCache();});}//缓存多个IFlyweight对象private Dictionary<string,IFlyweight> fwDic= new Dictionary<string,IFlyweight>();//缓存被共享对象的缓存配置private Dictionary<string,CacheConfModel>cacheConfDic= new Dictionary<string,CacheConfModel>();//记录缓存对象被引用的次数private Dictionary<string,int> countDic= new Dictionary<string,int>();//默认6秒钟,单位是毫秒【这个时间可以根据应用的要求来设置】private readonly long DURABLE_TIME = 6 * 1000L;private readonly object useCountLock = new object();//获取某个享元被使用的次数public int GetUseTimes(string key){lock (useCountLock){if (countDic.ContainsKey(key)){int count = countDic[key];return count;}else{return -1;}}}private readonly object flyweightLock = new object();//获取state对应的享元对象public IFlyweight GetIFlyweight(string state){lock(flyweightLock){if (!fwDic.ContainsKey(state)){IFlyweight flyweight = new AuthorizationFlyweight(state);AddInfoToDic(ref fwDic, state, flyweight);//同时设置引用计数AddInfoToDic(ref countDic, state, 1);//同时设置缓存配置数据CacheConfModel ccm = new CacheConfModel();//指获取当前时间与1970年1月1日00:00:00 GMT之间所差的毫秒数ccm.BeginTime = GetCurrentMills();ccm.Forever = false;ccm.DurableTime = DURABLE_TIME;AddInfoToDic(ref cacheConfDic,state,ccm);return flyweight;}else{IFlyweight flyweight = fwDic[state];//表示还在使用,那么应该重新设置缓存配置CacheConfModel ccm = cacheConfDic[state];ccm.BeginTime = GetCurrentMills();//设置回容器中AddInfoToDic(ref cacheConfDic, state, ccm);//同时计数加1int count = countDic[state];count++;AddInfoToDic(ref countDic,state,count);return flyweight;}}}private readonly object opcFlyweightLock = new object();//删除state对应的享元对象,且清除对应的缓存配置和引用次数记录private void RemoveFlyweight(string state){lock(opcFlyweightLock){fwDic.Remove(state);cacheConfDic.Remove(state);countDic.Remove(state);}}//维护清除缓存的方法private void ClearCache(){List<string> tmpList = new List<string>();while (true){foreach (var item in cacheConfDic){CacheConfModel ccm = item.Value;//比较是否需要清除long tmpTime = GetCurrentMills() - ccm.BeginTime;if (tmpTime >= ccm.DurableTime){//可以清除,先缓存下来tmpList.Add(item.Key);}}//真正清除for (int i = 0; i < tmpList.Count; i++){string state = tmpList[i];FlyweightFactory.Instance.RemoveFlyweight(state);}string strState = string.Empty;fwDic.ToList().ForEach(item => strState += $"【{item.Key}】");Console.WriteLine($"现在 缓存多个IFlyweight容器数量是【{fwDic.Count}】内容是{strState}");//休息2秒后再重新判断try{Thread.Sleep(1000);}catch (Exception ex){Console.WriteLine($"清除缓存的方法异常,异常内容是【{ex.Message}】");}}}//添加信息到字典中private void AddInfoToDic<T1,T2>(ref Dictionary<T1,T2> dic,T1 key, T2 value){if (key==null || key.Equals("") ||value==null || value.Equals("") || dic==null){return;}if (dic.ContainsKey(key)){dic[key] = value;}else{dic.Add(key, value);}}/// <summary>/// 获取当前时间与1970年1月1日00:00:00 GMT之间所差的毫秒数/// </summary>/// <returns></returns>private long GetCurrentMills(){//DateTime startDT = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);//long currentMills = (long)(DateTime.UtcNow-startDT).TotalMilliseconds;long currentMills = (long)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;return currentMills;}}//Class_end
}
2.4.7、实现安全管理类
要想实现引用计数的效果,安全管理类还需要进行一些修改,修改内容如下:
《1》去掉原有放置登录人员对应权限数据的缓存;
《2》不需要实现登录功能,直接去掉。
《3》原来通过字典获取值的地方直接通过QueryByUser获取。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FlyweightPattern.FlyweightDemoTwo;namespace FlyweightPattern.FlyweightDemoThree
{/// <summary>/// 安全管理类【单例】/// </summary>internal class SecurityMgr{//本类示例private static readonly Lazy<SecurityMgr> lazy = new Lazy<SecurityMgr>(() => new SecurityMgr());//本类单例public static SecurityMgr Instance { get { return lazy.Value; } }//禁止该类被外部newprivate SecurityMgr(){}//获取数据库人员信息TestDB testDB = new TestDB();//判断某用户对某个安全实体是否拥有某种权限public bool HasPermit(string user, string securityEntity, string permit){if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(securityEntity) ||string.IsNullOrEmpty(permit)){return false;}List<IFlyweight> col = this.QueryByUser(user);if (col == null && col?.Count == 0){Console.WriteLine($"【{user}】没有登录或是没有被分配任何权限");return false;}foreach (var item in col){//输出当前的实力,看看是否为同一个实例对象Console.WriteLine($"查询到【{item}】 该内容对应的唯一编号是【{item.GetHashCode()}】");if (item.Math(securityEntity, permit)){return true;}}return false;}//从数据库中获取某人所拥有的权限private List<IFlyweight> QueryByUser(string user){List<IFlyweight> col = new List<IFlyweight>();if (!string.IsNullOrEmpty(user)){foreach (var item in testDB.colDB){string[] strArray = item.Split(',');if (strArray[0].Equals(user)){IFlyweight fw = null;if (strArray[3].Equals("2")){//表示权限的组合(不共享的享元)fw = new UnsharedConcreteFlyweight();//获取需要组合的数据string combinationName = strArray[1];string[] strCombination = testDB.dicDB[combinationName];foreach (string combination in strCombination){IFlyweight fwtmp = FlyweightFactory.Instance.GetIFlyweight(combination);//把这个对象加入到组合对象中fw.Add(fwtmp);}}else{string state = $"{strArray[1]},{strArray[2]}";fw = FlyweightFactory.Instance.GetIFlyweight(state);}col.Add(fw);}}}return col;}}//Class_end
}
2.4.8、客户端测试
namespace FlyweightPattern
{internal class Program{static void Main(string[] args){TestFlyweightDemoThree();Console.ReadLine();}/// <summary>/// 测试享元模式示例三/// </summary>private static void TestFlyweightDemoThree(){Console.WriteLine("------测试享元模式示例三------");FlyweightDemoThree.SecurityMgr securityMgr=FlyweightDemoThree.SecurityMgr.Instance;bool f1 = securityMgr.HasPermit("张三","薪资数据","查看");Console.WriteLine($"【张三】对【薪资数据】拥有【查看】权限 结果【{f1}】\n");bool f2 = securityMgr.HasPermit("李四","薪资数据","查看");Console.WriteLine($"【李四】对【薪资数据】拥有【查看】权限 结果【{f2}】\n");bool f3 = securityMgr.HasPermit("李四","薪资数据","修改");Console.WriteLine($"【李四】对【薪资数据】拥有【修改】权限 结果【{f3}】\n");//然后检查是否具有其他内容的for (int i = 0; i < 3; i++){string user = $"张三{i}";bool res = securityMgr.HasPermit($"{user}", "人员列表", "查看");Console.WriteLine($"【{user}】对【人员列表】拥有【查看】权限结果是【{res}】\n");}//查看引用次数【这里的引用次数是指SecurityMgr里面的QueryByUser方法通过享元工厂区获取享元对象的次数】FlyweightDemoThree.FlyweightFactory flyweightFactory=FlyweightDemoThree.FlyweightFactory.Instance;string state1 = "薪资数据,查看";Console.WriteLine($"【{state1}】被引用了【{flyweightFactory.GetUseTimes(state1)}】次");string state2 = "薪资数据,修改";Console.WriteLine($"【{state2}】被引用了【{flyweightFactory.GetUseTimes(state2)}】次");string state3 = "人员列表,查看";Console.WriteLine($"【{state3}】被引用了【{flyweightFactory.GetUseTimes(state3)}】次");}}//Class_end
}
2.4.9、运行结果
三、项目源码工程
kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern