一、基礎概念
????????享元模式的本質是【分離與共享】。
序號 | 說明 |
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