【結構型模式十二】享元模式-1(Flyweight)

1 場景問題#

1.1 加入權限控制##

考慮這樣一個問題惶岭,給系統(tǒng)加入權限控制,這基本上是所有的應用系統(tǒng)都有的功能了。

對于應用系統(tǒng)而言聊替,一般先要登錄系統(tǒng),才可以使用系統(tǒng)的功能培廓,登錄過后惹悄,用戶的每次操作都需要經過權限系統(tǒng)的控制,確保該用戶有操作該功能的權限肩钠,同時還要控制該用戶對數(shù)據(jù)的訪問權限泣港、修改權限等等〖劢常總之一句話当纱,一個安全的系統(tǒng),需要對用戶的每一次操作都要做權限檢測踩窖,包括功能和數(shù)據(jù)坡氯,以確保只有獲得相應授權的人,才能執(zhí)行相應的功能,操作相應的數(shù)據(jù)箫柳。

舉個例子來說吧:普通人員都有能查看到本部門人員列表的權限颓遏,但是在人員列表中每個人員的薪資數(shù)據(jù),普通人員是不可以看到的滞时;而部門經理在查看本部門人員列表的時候叁幢,就可以看到每個人員相應的薪資數(shù)據(jù)。

現(xiàn)在就要來實現(xiàn)為系統(tǒng)加入權限控制的功能坪稽,該怎么實現(xiàn)呢曼玩?

為了讓大家更好的理解后面講述的知識,先介紹一點權限系統(tǒng)的基礎知識窒百。幾乎所有的權限系統(tǒng)都分成兩個部分黍判,一個是授權部分,一個是驗證部分篙梢,為了理解它們顷帖,首先解釋兩個基本的名詞:安全實體和權限

安全實體:就是被權限系統(tǒng)檢測的對象渤滞,比如工資數(shù)據(jù)贬墩。

權限:就是需要被校驗的權限對象,比如查看妄呕、修改等陶舞。

安全實體和權限通常要一起描述才有意義,比如有這么個描述:“現(xiàn)在要檢測登錄人員對工資數(shù)據(jù)是否有查看的權限”绪励, “工資數(shù)據(jù)”這個安全實體和“查看”這個權限一定要一起描述肿孵。如果只出現(xiàn)安全實體描述,那就變成這樣:“現(xiàn)在要檢測登錄人員對工資數(shù)據(jù)”疏魏,對工資數(shù)據(jù)干什么呀停做,沒有后半截,一看就知道不完整大莫;當然只有權限描述也不行蛉腌,那就變成:“現(xiàn)在要檢測登錄人員是否有查看的權限”,對誰的查看權限啊葵硕,也不完整眉抬。所以安全實體和權限通常要一起描述贯吓。

了解了上面兩個名詞懈凹,來看看什么是授權和驗證:

所謂授權是指:把對某些安全實體的某些權限分配給某些人員的過程。

所謂驗證是指:判斷某個人員對某個安全實體是否擁有某個或某些權限的過程悄谐。

也就是說介评,授權過程即是權限的分配過程,而驗證過程則是權限的匹配過程。在目前應用系統(tǒng)的開發(fā)中们陆,多數(shù)是利用數(shù)據(jù)庫來存放授權過程產生的數(shù)據(jù)寒瓦,也就是說:授權是向數(shù)據(jù)庫里面添加數(shù)據(jù)、或是維護數(shù)據(jù)的過程坪仇,而匹配過程就變成了從數(shù)據(jù)庫中獲取相應數(shù)據(jù)進行匹配的過程了杂腰。

為了讓問題相對簡化一點,就不去考慮權限的另外兩個特征椅文,一個是繼承性喂很,一個是最近匹配原則,都什么意思呢皆刺,還是解釋一下:

  1. 權限的繼承性指的是:如果多個安全實體存在包含關系少辣,而某個安全實體沒有相應的權限限制,那么它會繼承包含它的安全實體的相應權限羡蛾。

比如:某個大樓和樓內的房間都是安全實體漓帅,很明顯大樓這個安全實體會包含樓內的房間這些安全實體,可以認為大樓是樓內房間的父級實體〕赵梗現(xiàn)在來考慮一個具體的權限——進入某個房間的權限忙干。如果這個房間沒有門,也就是誰都可以進入浪藻,相當于這個房間對應的安全實體豪直,沒有進入房間的權限限制,那么是不是說所有的人都可以進入這個房間呢珠移?當然不是弓乙,某人能進入這個房間的前提是:這個人要有權限進入這個大樓,也就是說钧惧,這個時候房間這個安全實體暇韧,它本身沒有進入權限的限制,但是它會繼承父級安全實體的進入權限浓瞪。

  1. 權限的最近匹配原則指的是:如果多個安全實體存在包含關系懈玻,而某個安全實體沒有相應的權限限制,那么它會向上尋找并匹配相應權限限制乾颁,直到找到一個離這個安全實體最近的擁有相應權限限制的安全實體為止涂乌。如果把整個層次結構都尋找完了都沒有匹配到相應權限限制的話,那就說明所有人對這個安全實體都擁有這個相應的權限限制英岭。

繼續(xù)上面權限繼承性的例子湾盒,如果現(xiàn)在這個大樓是坐落在某個機關大院內,這就演變成了诅妹,要進入某個房間罚勾,首先要有進入大樓的權限毅人,要進入大樓又需要有能進入機關大院的權限。

所謂最近匹配原則就是尖殃,如果某個房間沒有門丈莺,也就意味著這個房間沒有進入的權限限制,那么它就會向上繼續(xù)尋找并匹配送丰,看看大樓有沒有進入的權限限制缔俄,如果有就使用這個權限限制,終止尋找器躏;如果沒有牵现,繼續(xù)向上尋找,直到找到一個匹配的為止邀桑。如果最后大院也沒有進入的權限限制瞎疼,那就變成所有人都可以進入到這個房間里面來了。

1.2 不使用模式的解決方案##

  1. 看看現(xiàn)在都已經有什么了

系統(tǒng)的授權工作已經完成壁畸,授權數(shù)據(jù)記錄在數(shù)據(jù)庫里面贼急,具體的數(shù)據(jù)結構就不去展開了,反正里面記錄了人員對安全實體所擁有的權限捏萍。假如現(xiàn)在系統(tǒng)中已有如下的授權數(shù)據(jù):

張三  對  人員列表   擁有    查看的權限
李四  對  人員列表   擁有    查看的權限
李四  對  薪資數(shù)據(jù)   擁有    查看的權限
李四  對  薪資數(shù)據(jù)   擁有    修改的權限
  1. 思路選擇

由于操作人員進行授權操作過后太抓,各人員被授予的權限是記錄在數(shù)據(jù)庫中的,剛開始有開發(fā)人員提出令杈,每次用戶操作系統(tǒng)的時候走敌,都直接到數(shù)據(jù)庫里面去動態(tài)查詢,以判斷該人員是否擁有相應的權限逗噩,但很快就被否決掉了掉丽,試想一下,用戶操作那么頻繁异雁,每次都到數(shù)據(jù)庫里面動態(tài)查詢捶障,這會嚴重加劇數(shù)據(jù)庫服務器的負擔,使系統(tǒng)變慢纲刀。

為了加快系統(tǒng)運行的速度项炼,開發(fā)小組決定采用一定的緩存,當每個人員登錄的時候示绊,就把該人員能操作的權限獲取到锭部,存儲在內存中,這樣每次操作的時候面褐,就直接在內存里面進行權限的校驗拌禾,速度會大大加快,這是典型的以空間換時間的做法盆耽。

  1. 實現(xiàn)示例

(1)首先定義描述授權數(shù)據(jù)的數(shù)據(jù)對象蹋砚,示例代碼如下:

/**
   * 描述授權數(shù)據(jù)的數(shù)據(jù)model
   */
public class AuthorizationModel {
      /**
       * 人員
       */
      private String user;
      /**
       * 安全實體
       */
      private String securityEntity;
      /**
       * 權限
       */
      private String permit;
      public String getUser() {
         return user;
      }
      public void setUser(String user) {
         this.user = user;
      }
      public String getSecurityEntity() {
         return securityEntity;
      }
      public void setSecurityEntity(String securityEntity) {
         this.securityEntity = securityEntity;
      }
      public String getPermit() {
         return permit;
      }
      public void setPermit(String permit) {
         this.permit = permit;
      }
}

(2)為了測試方便扼菠,做一個模擬的內存數(shù)據(jù)庫摄杂,把授權數(shù)據(jù)存儲在里面坝咐,用最簡單的字符串存儲的方式。示例代碼如下:

/**
   * 供測試用析恢,在內存中模擬數(shù)據(jù)庫中的值
   */
public class TestDB {
      /**
       * 用來存放授權數(shù)據(jù)的值
       */
      public static Collection<String> colDB = new ArrayList<String>();
      static{
         //通過靜態(tài)塊來填充模擬的數(shù)據(jù)    
         colDB.add("張三,人員列表,查看");
         colDB.add("李四,人員列表,查看");
         colDB.add("李四,薪資數(shù)據(jù),查看");
         colDB.add("李四,薪資數(shù)據(jù),修改");
         //增加更多的授權數(shù)據(jù)
         for(int i=0;i<3;i++){
             colDB.add("張三"+i+",人員列表,查看");
         }
      }  
}

(3)接下來實現(xiàn)登錄和權限控制的業(yè)務墨坚,示例代碼如下:

/**
   * 安全管理,實現(xiàn)成單例
   */
public class SecurityMgr {
      private static SecurityMgr securityMgr = new SecurityMgr();
      private SecurityMgr(){     
      }
      public static SecurityMgr getInstance(){
         return securityMgr;
      }
      /**
       * 在運行期間映挂,用來存放登錄人員對應的權限泽篮,
       * 在Web應用中,這些數(shù)據(jù)通常會存放到session中
       */
      private Map<String,Collection<AuthorizationModel>> map = new HashMap<String,Collection<AuthorizationModel>>();
  
      /**
       * 模擬登錄的功能
       * @param user 登錄的用戶
       */
      public void login(String user){
         //登錄時就需要把該用戶所擁有的權限柑船,從數(shù)據(jù)庫中取出來帽撑,放到緩存中去
         Collection<AuthorizationModel> col = queryByUser(user);
         map.put(user, col);
      }
      /**
       * 判斷某用戶對某個安全實體是否擁有某權限
       * @param user 被檢測權限的用戶
       * @param securityEntity 安全實體
       * @param permit 權限
       * @return true表示擁有相應權限,false表示沒有相應權限
       */
      public boolean hasPermit(String user,String securityEntity,String permit){
         Collection<AuthorizationModel> col = map.get(user);
         if(col==null || col.size()==0){
             System.out.println(user+"沒有登錄或是沒有被分配任何權限");
             return false;
         }
         for(AuthorizationModel am : col){
             //輸出當前實例鞍时,看看是否同一個實例對象
             System.out.println("am=="+am);
             if(am.getSecurityEntity().equals(securityEntity) && am.getPermit().equals(permit)){
                return true;
             }
         }
         return false;
      }
      /**
       * 從數(shù)據(jù)庫中獲取某人所擁有的權限
       * @param user 需要獲取所擁有的權限的人員
       * @return 某人所擁有的權限
       */
      private Collection<AuthorizationModel> queryByUser(String user){
         Collection<AuthorizationModel> col = new ArrayList<AuthorizationModel>();
         for(String s : TestDB.colDB){
             String ss[] = s.split(",");
             if(ss[0].equals(user)){
                AuthorizationModel am = new AuthorizationModel();
                am.setUser(ss[0]);
                am.setSecurityEntity(ss[1]);
                am.setPermit(ss[2]);
            
                col.add(am);
             }
         }
         return col;
      }
}

(4)好不好用呢亏拉,寫個客戶端來測試一下,示例代碼如下:

public class Client {
      public static void main(String[] args) {
         //需要先登錄逆巍,然后再判斷是否有權限
         SecurityMgr mgr = SecurityMgr.getInstance();
         mgr.login("張三");
         mgr.login("李四");   
         boolean f1 = mgr.hasPermit("張三","薪資數(shù)據(jù)","查看");
         boolean f2 = mgr.hasPermit("李四","薪資數(shù)據(jù)","查看");     
     
         System.out.println("f1=="+f1);
         System.out.println("f2=="+f2);
         for(int i=0;i<3;i++){
             mgr.login("張三"+i);
             mgr.hasPermit("張三"+i,"薪資數(shù)據(jù)","查看");
         }
      }
}

運行結果如下:

am==cn.javass.dp.flyweight.example1.AuthorizationModel@1eed786
am==cn.javass.dp.flyweight.example1.AuthorizationModel@187aeca
am==cn.javass.dp.flyweight.example1.AuthorizationModel@e48e1b
f1==false
f2==true
am==cn.javass.dp.flyweight.example1.AuthorizationModel@12dacd1
am==cn.javass.dp.flyweight.example1.AuthorizationModel@119298d
am==cn.javass.dp.flyweight.example1.AuthorizationModel@f72617

輸出結果中的f1為false及塘,表示張三對薪資數(shù)據(jù)沒有查看的權限;而f2為true锐极,表示李四對對薪資數(shù)據(jù)有查看的權限笙僚,是正確的,基本完成了功能灵再。

1.3 有何問題##

看了上面的實現(xiàn)肋层,很簡單,而且還考慮了性能的問題翎迁,在內存中緩存了每個人相應的權限數(shù)據(jù)槽驶,使得每次判斷權限的時候,速度大大加快鸳兽,實現(xiàn)得挺不錯掂铐,難道有什么問題嗎?

仔細想想揍异,問題就來了全陨,既有緩存這種方式固有的問題,也有我們自己實現(xiàn)上的問題衷掷。先說說緩存固有的問題吧辱姨,這個不在本次討論之列,大家了解一下戚嗅。

  1. 緩存時間長度的問題

這些數(shù)據(jù)應該被緩存多久雨涛,如果是Web應用枢舶,這種跟登錄人員相關的權限數(shù)據(jù),多是放在session中進行緩存替久,這樣session超時的時候凉泄,就會被清除掉。如果不是Web應用呢蚯根?就得自己來控制了后众,另外就算是在Web應用中,也不一定非要緩存到session超時才清除颅拦。總之蒂誉,控制緩存數(shù)據(jù)應該被緩存多長時間,是實現(xiàn)高效緩存的一個問題點距帅。

  1. 緩存數(shù)據(jù)和真實數(shù)據(jù)的同步問題

這里的同步是指的數(shù)據(jù)同步右锨,不是多線程的同步。比如:上面的授權數(shù)據(jù)是存放在數(shù)據(jù)庫里的碌秸,運行的時候緩存到內存里面绍移,如果真實的授權數(shù)據(jù)在運行期間發(fā)生了改變,那么緩存里的數(shù)據(jù)就應該和數(shù)據(jù)庫的數(shù)據(jù)同步哮肚,以保持一致登夫,否則數(shù)據(jù)就錯了。如何合理的同步數(shù)據(jù)允趟,也是實現(xiàn)高效緩存的一個問題點恼策。

  1. 緩存的多線程并發(fā)控制

對于緩存的數(shù)據(jù),有些操作從里面取值潮剪,有些操作向緩存里面添加值涣楷,有些操作在清除過期的緩存數(shù)據(jù),有些操作在進行緩存和真實數(shù)據(jù)的同步抗碰,在一個多線程的環(huán)境下狮斗,如何合理的對緩存進行并發(fā)控制,也是實現(xiàn)高效緩存的一個問題點弧蝇。

先簡單提這么幾個碳褒,事實上,實現(xiàn)合理看疗、高效的緩存也不是一件很輕松的事情沙峻,好在這些問題,都不在我們這次的討論之列两芳,這里的重心還是來講述模式摔寨,而不是緩存實現(xiàn)。

再來看看前面實現(xiàn)上的問題怖辆,仔細觀察在上面輸出結果中框住的部分是复,這些值是輸出對象實例得到的删顶,默認輸出的是對象的hashCode值,而默認的hashCode值可以用來判斷是不是同一對象實例淑廊。在Java中逗余,默認的equals方法比較的是內存地址,而equals方法和hashCode方法的關系是:equals方法返回true的話蒋纬,那么這兩個對象實例的hashCode必須相同猎荠;而hashCode相同坚弱,equals方法并不一定返回true蜀备,也就是說兩個對象實例不一定是同一對象實例。換句話說荒叶,如果hashCode不同的話碾阁,鐵定不是同一個對象實例。

仔細看看上面輸出結果些楣,框住部分的值是不同的脂凶,表明這些對象實例肯定不是同一個對象實例,而是多個對象實例愁茁。這就引出一個問題了蚕钦,就是對象實例數(shù)目太多,為什么這么說呢鹅很?看看就描述這么幾條數(shù)據(jù)嘶居,數(shù)數(shù)看有多少個對象實例呢?目前是一條數(shù)據(jù)就有一個對象實例促煮,這很恐怖邮屁,數(shù)據(jù)庫的數(shù)據(jù)量是很大的,如果有幾萬條菠齿,幾十萬條佑吝,豈不是需要幾萬個,甚至幾十萬個對象實例绳匀,這會耗費掉大量的內存芋忿。

另外,這些對象的粒度都很小疾棵,都是簡單的描述某一個方面的對象戈钢,而且很多數(shù)據(jù)是重復的,在這些大量重復的數(shù)據(jù)上耗費掉了很多的內存陋桂。比如在前面示例的數(shù)據(jù)中就會發(fā)現(xiàn)有重復的部分逆趣,見下面框住的部分:

張三  對  人員列表   擁有    查看的權限
李四  對  人員列表   擁有    查看的權限
李四  對  薪資數(shù)據(jù)   擁有    查看的權限
李四  對  薪資數(shù)據(jù)   擁有    修改的權限

前面講過,對于安全實體和權限一般要聯(lián)合描述嗜历,因此對于“人員列表 這個安全實體 的 查看權限 限制”宣渗,就算是授權給不同的人員抖所,這個描述是一樣的。假設在某極端情況下痕囱,要把“人員列表 這個安全實體 的 查看權限 限制”授權給一萬個人田轧,那么數(shù)據(jù)庫里面會有一萬條記錄,按照前面的實現(xiàn)方式鞍恢,會有一萬個對象實例傻粘,而這些實例里面,有大部分的數(shù)據(jù)是重復的帮掉,而且會重復一萬次弦悉,你覺得這是不是個很大的問題呢?

把上面的問題描述出來就是:在系統(tǒng)當中蟆炊,存在大量的細粒度對象稽莉,而且存在大量的重復數(shù)據(jù),嚴重耗費內存涩搓,如何解決污秆?

2 解決方案#

2.1 享元模式來解決##

用來解決上述問題的一個合理的解決方案就是享元模式。那么什么是享元模式呢昧甘?

  1. 享元模式定義
享元模式定義
  1. 應用享元模式來解決的思路

仔細觀察和分析上面的授權信息良拼,會發(fā)現(xiàn)有一些數(shù)據(jù)是重復出現(xiàn)的,比如:人員列表充边、薪資數(shù)據(jù)庸推、查看、修改等等痛黎。至于人員相關的數(shù)據(jù)予弧,考慮到每個描述授權的對象都是和某個人員相關的,所以存放的時候湖饱,會把相同人員的授權信息組織在一起掖蛤,就不去考慮人員數(shù)據(jù)的重復性了。

現(xiàn)在造成內存浪費的主要原因:就是細粒度對象太多井厌,而且有大量重復的數(shù)據(jù)蚓庭。如果能夠有效的減少對象的數(shù)量,減少重復的數(shù)據(jù)仅仆,那么就能夠節(jié)省不少內存器赞。一個基本的思路就是緩存這些包含著重復數(shù)據(jù)的對象,讓這些對象只出現(xiàn)一次墓拜,也就只耗費一份內存了港柜。

但是請注意,并不是所有的對象都適合緩存,因為緩存的是對象的實例夏醉,實例里面存放的主要是對象屬性的值爽锥。因此飞涂,如果被緩存的對象的屬性值經常變動苏揣,那就不適合緩存了,因為真實對象的屬性值變化了撼短,那么緩存里面的對象也必須要跟著變化靶擦,否則緩存中的數(shù)據(jù)就跟真實對象的數(shù)據(jù)不同步腮考,可以說是錯誤的數(shù)據(jù)了。

因此玄捕,需要分離出被緩存對象實例中踩蔚,哪些數(shù)據(jù)是不變且重復出現(xiàn)的,哪些數(shù)據(jù)是經常變化的桩盲,真正應該被緩存的數(shù)據(jù)是那些不變且重復出現(xiàn)的數(shù)據(jù)寂纪,把它們稱為對象的內部狀態(tài)席吴,而那些變化的數(shù)據(jù)就不緩存了赌结,把它們稱為對象的外部狀態(tài)

這樣在實現(xiàn)的時候孝冒,把內部狀態(tài)分離出來共享柬姚,稱之為享元,通過共享享元對象來減少對內存的占用庄涡。把外部狀態(tài)分離出來量承,放到外部,讓應用在使用的時候進行維護穴店,并在需要的時候傳遞給享元對象使用撕捍。為了控制對內部狀態(tài)的共享,并且讓外部能簡單的使用共享數(shù)據(jù)泣洞,提供一個工廠來管理享元忧风,把它稱為享元工廠

2.2 模式結構和說明##

享元模式的結構如圖20.1所示:

享元模式的結構

Flyweight:享元接口球凰,通過這個接口flyweight可以接受并作用于外部狀態(tài)狮腿。通過這個接口傳入外部的狀態(tài),在享元對象的方法處理中可能會使用這些外部的數(shù)據(jù)呕诉。

ConcreteFlyweight:具體的享元實現(xiàn)對象缘厢,必須是可共享的,需要封裝flyweight的內部狀態(tài)甩挫。

UnsharedConcreteFlyweight:非共享的享元實現(xiàn)對象贴硫,并不是所有的Flyweight實現(xiàn)對象都需要共享。非共享的享元實現(xiàn)對象通常是對共享享元對象的組合對象伊者。

FlyweightFactory:享元工廠英遭,主要用來創(chuàng)建并管理共享的享元對象拖刃,并對外提供訪問共享享元的接口。

Client:享元客戶端贪绘,主要的工作是維持一個對flyweight的引用兑牡,計算或存儲享元對象的外部狀態(tài),當然這里可以訪問共享和不共享的flyweight對象税灌。

2.3 享元模式示例代碼##

  1. 先看享元的接口定義均函,通過這個接口flyweight可以接受并作用于外部狀態(tài),示例代碼如下:
/**
   * 享元接口菱涤,通過這個接口享元可以接受并作用于外部狀態(tài)
   */
public interface Flyweight {
      /**
       * 示例操作苞也,傳入外部狀態(tài)
       * @param extrinsicState 示例參數(shù),外部狀態(tài)
       */
      public void operation(String extrinsicState);
}
  1. 接下來看看具體的享元接口的實現(xiàn)粘秆,先看共享享元的實現(xiàn)如迟,封裝flyweight的內部狀態(tài),當然也可以提供功能方法攻走,示例代碼如下:
/**
   * 享元對象
   */
public class ConcreteFlyweight implements Flyweight{
      /**
       * 示例殷勘,描述內部狀態(tài)
       */
      private String intrinsicState;
      /**
       * 構造方法,傳入享元對象的內部狀態(tài)的數(shù)據(jù)
       * @param state 享元對象的內部狀態(tài)的數(shù)據(jù)
       */
      public ConcreteFlyweight(String state){
         this.intrinsicState = state;
      }

      public void operation(String extrinsicState) {
         //具體的功能處理昔搂,可能會用到享元內部玲销、外部的狀態(tài)
      }  
}

再看看不需要共享的享元對象的實現(xiàn),并不是所有的Flyweight對象都需要共享摘符,F(xiàn)lyweight接口使共享成為可能贤斜,但并不強制共享。示例代碼如下:

/**
   * 不需要共享的flyweight對象逛裤,
   * 通常是將被共享的享元對象作為子節(jié)點瘩绒,組合出來的對象
   */
public class UnsharedConcreteFlyweight implements Flyweight{
      /**
       * 示例,描述對象的狀態(tài)
       */
      private String allState;
  
      public void operation(String extrinsicState) {
         // 具體的功能處理
      }
}
  1. 在享元模式中带族,客戶端不能直接創(chuàng)建共享的享元對象實例锁荔,必須通過享元工廠來創(chuàng)建。接下來看看享元工廠的實現(xiàn)炉菲,示例代碼如下:
/**
   * 享元工廠
   */
public class FlyweightFactory {
      /**
       * 緩存多個flyweight對象堕战,這里只是示意一下
       */
      private Map<String,Flyweight> fsMap = new HashMap<String,Flyweight>();
      /**
       * 獲取key對應的享元對象
       * @param key 獲取享元對象的key,只是示意
       * @return key 對應的享元對象
       */
      public Flyweight getFlyweight(String key) {
         //這個方法里面基本的實現(xiàn)步驟如下:      
         //1:先從緩存里面查找拍霜,是否存在key對應的Flyweight對象
         Flyweight f = fsMap.get(key);

         //2:如果存在嘱丢,就返回相對應的Flyweight對象
         if(f==null){
             //3:如果不存在
             //3.1:創(chuàng)建一個新的Flyweight對象
             f = new ConcreteFlyweight(key);
             //3.2:把這個新的Flyweight對象添加到緩存里面
             fsMap.put(key,f);
             //3.3:然后返回這個新的Flyweight對象
         }

         return f;
      }
}
  1. 最后來看看客戶端的實現(xiàn),客戶端通常會維持一個對flyweight的引用祠饺,計算或存儲一個或多個flyweight的外部狀態(tài)越驻。示例代碼如下:
/**
   * Client對象,通常會維持一個對flyweight的引用,
   * 計算或存儲一個或多個flyweight的外部狀態(tài)
   */
public class Client {
      //具體的功能處理
}

2.4 使用享元模式重寫示例##

再次分析上面的授權信息缀旁,實際上重復出現(xiàn)的數(shù)據(jù)主要是對安全實體和權限的描述记劈,又考慮到安全實體和權限的描述一般是不分開的,那么找出這些重復的描述并巍,比如:人員列表的查看權限目木。而且這些重復的數(shù)據(jù)是可以重用的,比如給它們配上不同的人員懊渡,就可以組合成為不同的授權描述刽射,如圖20.2所示:

授權描述示意圖

圖20.2就可以描述如下的信息:

張三  對  人員列表   擁有    查看的權限
李四  對  人員列表   擁有    查看的權限
王五  對  人員列表   擁有    查看的權限

很明顯,可以把安全實體和權限的描述定義成為享元剃执,而和它們結合的人員數(shù)據(jù)誓禁,就可以做為享元的外部數(shù)據(jù)。為了演示簡單肾档,就把安全實體對象和權限對象簡化成了字符串摹恰,描述一下它們的名字。

  1. 按照享元模式怒见,也為了系統(tǒng)的擴展性和靈活性俗慈,給享元定義一個接口,外部使用享元還是面向接口來編程速种,示例代碼如下:
/**
   * 描述授權數(shù)據(jù)的享元接口
   */
public interface Flyweight {
      /**
       * 判斷傳入的安全實體和權限姜盈,是否和享元對象內部狀態(tài)匹配
       * @param securityEntity 安全實體
       * @param permit 權限
       * @return true表示匹配,false表示不匹配
       */
      public boolean match(String securityEntity,String permit);
}
  1. 定義了享元接口配阵,該來實現(xiàn)享元對象了,這個對象需要封裝授權數(shù)據(jù)中重復出現(xiàn)部分的數(shù)據(jù)示血,示例代碼如下:
/**
   * 封裝授權數(shù)據(jù)中重復出現(xiàn)部分的享元對象
   */
public class AuthorizationFlyweight implements Flyweight{
      /**
       * 內部狀態(tài)棋傍,安全實體
       */
      private String securityEntity;
      /**
       * 內部狀態(tài),權限
       */
      private String permit;
      /**
       * 構造方法难审,傳入狀態(tài)數(shù)據(jù)
       * @param state 狀態(tài)數(shù)據(jù)瘫拣,包含安全實體和權限的數(shù)據(jù),用","分隔
       */
      public AuthorizationFlyweight(String state){
         String ss[] = state.split(",");
         securityEntity = ss[0];
         permit = ss[1];
      }
  
      public String getSecurityEntity() {
         return securityEntity;
      }
      public String getPermit() {
         return permit;
      }

      public boolean match(String securityEntity, String permit) {
         if(this.securityEntity.equals(securityEntity) && this.permit.equals(permit)){
             return true;
         }
         return false;
      }  
}
  1. 定義好了享元告喊,來看看如何管理這些享元麸拄,提供享元工廠來負責享元對象的共享管理和對外提供訪問享元的接口。

享元工廠一般不需要很多個黔姜,實現(xiàn)成為單例即可拢切。享元工廠負責享元對象的創(chuàng)建和管理,基本的思路就是在享元工廠里面緩存享元對象秆吵。在Java中最常用的緩存實現(xiàn)方式淮椰,就是定義一個Map來存放緩存的數(shù)據(jù),而享元工廠對外提供的訪問享元的接口,基本上就是根據(jù)key值到緩存的Map中獲取相應的數(shù)據(jù)主穗,這樣只要有了共享泻拦,同一份數(shù)據(jù)就可以重復使用了,示例代碼如下:

/**
   * 享元工廠忽媒,通常實現(xiàn)成為單例
   */
public class FlyweightFactory {
      private static FlyweightFactory factory = new FlyweightFactory();
      private FlyweightFactory(){    
      }
      public static FlyweightFactory getInstance(){
         return factory;
      }
      /**
       * 緩存多個flyweight對象
       */
      private Map<String,Flyweight> fsMap = new HashMap<String,Flyweight>();
      /**
       * 獲取key對應的享元對象
       * @param key 獲取享元對象的key
       * @return key對應的享元對象
       */
      public Flyweight getFlyweight(String key) {
         Flyweight f = fsMap.get(key);
         if(f==null){
             f = new AuthorizationFlyweight(key);
             fsMap.put(key,f);
         }
         return f;
      }
}
  1. 使用享元對象

實現(xiàn)完享元工廠争拐,該來看看如何使用享元對象了。按照前面的實現(xiàn)晦雨,需要一個對象來提供安全管理的業(yè)務功能陆错,就是前面的那個SecurityMgr類,這個類現(xiàn)在在享元模式中金赦,就充當了Client的角色音瓷,注意:這個Client角色和我們平時說的測試客戶端是兩個概念,這個Client角色是使用享元的對象夹抗。

SecurityMgr的實現(xiàn)方式基本上模仿前面的實現(xiàn)绳慎,也會有相應的改變,變化大致如下:

緩存的每個人員的權限數(shù)據(jù)漠烧,類型變成了Flyweight的了杏愤;

在原來queryByUser方法里面,通過new來創(chuàng)建授權對象的地方已脓,修改成了通過享元工廠來獲取享元對象珊楼,這是使用享元模式最重要的一點改變,也就是不是直接去創(chuàng)建對象實例度液,而是通過享元工廠來獲取享元對象實例厕宗;

示例代碼如下:

/**
   * 安全管理,實現(xiàn)成單例
   */
public class SecurityMgr {
      private static SecurityMgr securityMgr = new SecurityMgr();
      private SecurityMgr(){     
      }
      public static SecurityMgr getInstance(){
         return securityMgr;
      }  
      /**
       * 在運行期間堕担,用來存放登錄人員對應的權限已慢,
       * 在Web應用中,這些數(shù)據(jù)通常會存放到session中
       */
      private Map<String,Collection<Flyweight>> map = new HashMap<String,Collection<Flyweight>>();
      /**
       * 模擬登錄的功能
       * @param user 登錄的用戶
       */
      public void login(String user){
         //登錄時就需要把該用戶所擁有的權限霹购,從數(shù)據(jù)庫中取出來佑惠,放到緩存中去
         Collection<Flyweight> col = queryByUser(user);
         map.put(user, col);
      }
      /**
       * 判斷某用戶對某個安全實體是否擁有某權限
       * @param user 被檢測權限的用戶
       * @param securityEntity 安全實體
       * @param permit 權限
       * @return true表示擁有相應權限,false表示沒有相應權限
       */
      public boolean hasPermit(String user,String securityEntity,String permit){
         Collection<Flyweight> col = map.get(user);
         if(col==null || col.size()==0){
             System.out.println(user+"沒有登錄或是沒有被分配任何權限");
             return false;
         }
         for(Flyweight fm : col){
             //輸出當前實例齐疙,看看是否同一個實例對象
             System.out.println("fm=="+fm);
             if(fm.match(securityEntity, permit)){
                return true;
             }
         }
         return false;
      }
      /**
       * 從數(shù)據(jù)庫中獲取某人所擁有的權限
       * @param user 需要獲取所擁有的權限的人員
       * @return 某人所擁有的權限
       */
      private Collection<Flyweight> queryByUser(String user){
         Collection<Flyweight> col = new ArrayList<Flyweight>();
         for(String s : TestDB.colDB){
             String ss[] = s.split(",");
             if(ss[0].equals(user)){
                Flyweight fm = FlyweightFactory.getInstance().getFlyweight(ss[1]+","+ss[2]);
                col.add(fm);
             }
         }
         return col;
      }
}
  1. 所用到的TestDB沒有任何變化膜楷,這里就不去贅述了。

  2. 客戶端測試代碼也沒有任何變化贞奋,也不去贅述了赌厅。

運行測試一下,看看效果忆矛,主要是看看是不是能有效地減少那些重復數(shù)據(jù)對象的數(shù)量察蹲。運行結果如下:

fm==cn.javass.dp.flyweight.example3.AuthorizationFlyweight@e48e1b
fm==cn.javass.dp.flyweight.example3.AuthorizationFlyweight@e48e1b
fm==cn.javass.dp.flyweight.example3.AuthorizationFlyweight@12dacd1
f1==false
f2==true
fm==cn.javass.dp.flyweight.example3.AuthorizationFlyweight@e48e1b
fm==cn.javass.dp.flyweight.example3.AuthorizationFlyweight@e48e1b
fm==cn.javass.dp.flyweight.example3.AuthorizationFlyweight@e48e1b

仔細觀察結果中框住的部分请垛,會發(fā)現(xiàn)六條數(shù)據(jù)中,有五條的hashCode是同一個值洽议,根據(jù)我們的實現(xiàn)宗收,可以斷定這是同一個對象。也就是說亚兄,現(xiàn)在只有兩個對象實例混稽,而前面的實現(xiàn)中有六個對象實例。

如同示例的那樣审胚,對于封裝安全實體和權限的這些細粒度對象匈勋,既是授權分配的單元對象,也是權限檢測的單元對象膳叨∏⒔啵可能有很多人對某個安全實體擁有某個權限,如果為每個人都重新創(chuàng)建一個對象來描述對應的安全實體和權限菲嘴,那樣就太浪費內存空間了饿自。

通過共享封裝了安全實體和權限的對象,無論多少人擁有這個權限龄坪,實際的對象實例都是只有一個昭雌,這樣既減少了對象的數(shù)目,又節(jié)省了寶貴的內存空間健田,從而解決了前面提出的問題烛卧。

3 模式講解#

3.1 認識享元模式##

  1. 變與不變

享元模式設計的重點就在于分離變與不變,把一個對象的狀態(tài)分成內部狀態(tài)和外部狀態(tài)妓局,內部狀態(tài)是不變的总放,外部狀態(tài)是可變的。然后通過共享不變的部分跟磨,達到減少對象數(shù)量间聊、并節(jié)約內存的目的。在享元對象需要的時候抵拘,可以從外部傳入外部狀態(tài)給共享的對象,共享對象會在功能處理的時候型豁,使用自己內部的狀態(tài)和這些外部的狀態(tài)僵蛛。

事實上,分離變與不變是軟件設計上最基本的方式之一迎变,比如預留接口充尉,為什么在這個地方要預留接口,一個常見的原因就是這里存在變化衣形,可能在今后需要擴展驼侠、或者是改變已有的實現(xiàn)姿鸿,因此預留接口做為“可插入性的保證”。

  1. 共享與不共享

在享元模式中倒源,享元對象又有共享與不共享之分苛预,這種情況通常出現(xiàn)在跟組合模式合用的情況,通常共享的是葉子對象笋熬,一般不共享的部分是由共享部分組合而成的热某,由于所有細粒度的葉子對象都已經緩存了,那么緩存組合對象就沒有什么意義了胳螟。這個在后面給大家一個示例昔馋。

  1. 內部狀態(tài)和外部狀態(tài)

享元模式的內部狀態(tài),通常指的是包含在享元對象內部的糖耸、對象本身的狀態(tài)秘遏,通常是獨立于使用享元的場景的信息,一般創(chuàng)建過后就不再變化的狀態(tài)嘉竟,因此可以共享邦危。

外部狀態(tài)指的是享元對象之外的狀態(tài),取決于使用享元的場景周拐,會根據(jù)使用場景而變化铡俐,因此不可共享。如果享元對象需要這些外部狀態(tài)的話妥粟,可以從外部傳遞到享元對象里面审丘,比如通過方法的參數(shù)來傳遞。

也就是說享元模式真正緩存和共享的數(shù)據(jù)是享元的內部狀態(tài)勾给,而外部狀態(tài)是不應該被緩存共享的滩报。

另外一點,內部狀態(tài)和外部狀態(tài)是獨立的播急,外部狀態(tài)的變化不應該影響到內部狀態(tài)脓钾。

  1. 實例池

在享元模式中,為了創(chuàng)建和管理共享的享元部分桩警,引入了享元工廠可训,享元工廠中一般都包含有享元對象的實例池,享元對象就是緩存在這個實例池中的捶枢。

簡單介紹一點實例池的知識握截,所謂實例池,指的是緩存和管理對象實例的程序烂叔,通常實例池會提供對象實例的運行環(huán)境谨胞,并控制對象實例的生命周期

工業(yè)級的實例池實現(xiàn)上有兩個最基本的難點蒜鸡,一個就是動態(tài)控制實例數(shù)量胯努,一個就是動態(tài)分配實例來提供給外部使用牢裳。這些都是需要算法來做保證的。

假如實例池里面已有了3個實例叶沛,但是客戶端請求非常多蒲讯,有些忙不過來,那么實例池的管理程序就應該判斷出來恬汁,到底幾個實例才能滿足現(xiàn)在的客戶需求伶椿,理想狀況是剛剛好,就是既能夠滿足應用的需要氓侧,又不會造成對象實例的浪費脊另,假如經過判斷5個實例正好,那么實例池的管理程序就應該能動態(tài)的創(chuàng)建2個新的實例约巷。

這樣運行了一段時間偎痛,客戶端的請求減少了,這個時候實例池的管理程序又應該動態(tài)的判斷独郎,究竟幾個實例是最好的踩麦,多了明顯浪費資源,假如經過判斷只需要1個實例就可以了氓癌,那么實例池的管理程序應該銷毀掉多余的4個實例谓谦,以釋放資源。這就是動態(tài)控制實例數(shù)量贪婉。

對于動態(tài)分配實例反粥,也說明一下吧,假如實例池里面有3個實例疲迂,這個時候來了一個新的請求才顿,到底調度哪一個實例去執(zhí)行客戶的請求呢,如果有空閑實例尤蒿,那就是它了郑气,要是沒有空閑實例呢,是新建一個實例腰池,還是等待運行中的實例尾组,等它運行完了就來處理這個請求呢?具體如何調度示弓,也是需要算法來保障的演怎。

回到享元模式中來,享元工廠中的實例池可沒有這么復雜避乏,因為共享的享元對象基本上都是一個實例,一般不會出現(xiàn)同一個享元對象有多個實例的情況甘桑,這樣就不用去考慮動態(tài)創(chuàng)建和銷毀享元對象實例的功能拍皮;另外只有一個實例歹叮,也就不存在動態(tài)調度的麻煩,反正就是它了铆帽。

這也主要是因為享元對象封裝的多半是對象的內部狀態(tài)咆耿,這些狀態(tài)通常是不變的,有一個實例就夠了爹橱,不需要動態(tài)控制生命周期萨螺,也不需要動態(tài)調度,它只需要做一個緩存而已愧驱,沒有上升到真正的實例池那么個高度慰技。

  1. 享元模式的調用順序示意圖

享元模式的使用上,有兩種情況组砚,一種是沒有“不需要共享”的享元對象吻商,就如同前面的示例那樣,只有共享享元對象的情況糟红;還有一種是既有共享享元對象艾帐,又有不需要共享的享元對象的情況,這種情況后面再示例盆偿。

這里看看只有共享享元對象的情況下柒爸,享元模式的調用順序,如圖20.3所示:

只有共享享元對象的情況下事扭,享元模式的調用順序
  1. 誰來初始化共享對象

在享元模式中捎稚,通常是在第一次向享元工廠請求獲取共享對象的時候,進行共享對象的初始化句旱,而且多半都是在享元工廠內部實現(xiàn)阳藻,不會從外部傳入共享對象。當然可以從外部傳入一些創(chuàng)建共享對象需要的值谈撒,享元工廠可以按照這些值去初始化需要共享的對象腥泥,然后就把創(chuàng)建好的共享對象的實例放入享元工廠內部的緩存中,以后再請求這個共享對象的時候就不用再創(chuàng)建了啃匿。

3.2 不需要共享的享元實現(xiàn)##

可能有些朋友看到這個標題會很疑惑蛔外,享元不就是要共享的對象嗎?不共享溯乒,叫什么享元凹醒帷?

確實有不需要共享的享元實現(xiàn)裆悄,這種情況多出現(xiàn)在組合結構中矛纹,對于使用已經緩存的享元組合出來的對象,就沒有必要再緩存了光稼,也就是把已經緩存的享元當做葉子結點或南,組合出來的組合對象就不需要再被緩存了孩等。也把這種享元稱為復合享元。

比如上面的權限描述采够,如果出現(xiàn)組合權限描述肄方,在這個組合對象里面包含很多個共享的權限描述,那么這個組合對象就不用緩存了蹬癌,這個組合對象的存在只是為了在授權的時候更加方便权她。

具體點說吧,比如要給某人分配“薪資數(shù)據(jù)”這個安全實體的“修改”權限逝薪,那么一定會把“薪資數(shù)據(jù)”的“查看權限”也分配給這個人隅要,如果按照前面的做法,這就需要分配兩個對象翼闽,為了方便拾徙,干脆把這兩個描述組合起來,打包成一個對象感局,命名成為“操作薪資數(shù)據(jù)”尼啡,那么分配權限的時候,可以這么描述:

把  “操作薪資數(shù)據(jù)”  分配給   張三

這句話的意思就相當于

把  “薪資數(shù)據(jù)” 的  “查看”權限   分配給   張三
把  “薪資數(shù)據(jù)” 的  “修改”權限   分配給   張三

這樣一來询微,“操作薪資數(shù)據(jù)”就相當于是一個不需要共享的享元崖瞭,它實際由享元“薪資數(shù)據(jù) 的 查看 權限”,和享元“薪資數(shù)據(jù) 的 修改 權限”這兩個享元組合而成撑毛,因此“操作薪資數(shù)據(jù)”本身也就不需要再共享了书聚。

這樣分配權限的時候就會簡單一點。

但是這種組合對象藻雌,在權限系統(tǒng)中一般不用于驗證雌续,也就是說驗證的時候還是一個一個進行判斷,因為在存儲授權信息的時候是一條一條存儲的胯杭。但也不排除有些時候始終要檢查多個權限驯杜,干脆把這些權限打包,然后直接驗證是否有這個組合權限做个,只是這種情況應用得比較少而已鸽心。

還是用示例來說明吧,在上面已經實現(xiàn)的系統(tǒng)里面添加不需要共享的享元實現(xiàn)居暖。此時系統(tǒng)結構如圖20.4所示:

不需要共享享元的示例機構示意圖
  1. 首先要在享元接口上添加上對組合對象的操作顽频,主要是要添加向組合對象中加入子對象的方法,示例代碼如下:
/**
   * 描述授權數(shù)據(jù)的享元接口
   */
public interface Flyweight {
      /**
       * 判斷傳入的安全實體和權限太闺,是否和享元對象內部狀態(tài)匹配
       * @param securityEntity 安全實體
       * @param permit 權限
       * @return true表示匹配糯景,false表示不匹配
       */
      public boolean match(String securityEntity,String permit);
      /**
       * 為flyweight添加子flyweight對象
       * @param f 被添加的子flyweight對象
       */
      public void add(Flyweight f);  
}
  1. 享元接口改變了,那么原來共享的享元對象也需要實現(xiàn)這個方法,這個方法主要是針對組合對象的莺奸,因此在葉子對象里面拋出不支持的例外就好了丑孩,示例代碼如下:
/**
   * 封裝授權數(shù)據(jù)中重復出現(xiàn)部分的享元對象
   */
public class AuthorizationFlyweight implements Flyweight{
  
      public void add(Flyweight f) {
         throw new UnsupportedOperationException("對象不支持這個功能");
      }
}
  1. 接下來實現(xiàn)新的不需要共享的享元對象,其實就是組合共享享元對象的對象灭贷,這個組合對象中,需要保存所有的子對象略贮,另外它在實現(xiàn)match方法的時候甚疟,是通過遞歸的方式,在整個組合結構中進行匹配逃延。示例代碼如下:
/**
   * 不需要共享的享元對象的實現(xiàn)览妖,也是組合模式中的組合對象
   */
public class UnsharedConcreteFlyweight implements Flyweight{
      /**
       * 記錄每個組合對象所包含的子組件
       */
      private List<Flyweight> list = new ArrayList<Flyweight>();
  
      public void add(Flyweight f) {
         list.add(f);
      }
  
      public boolean match(String securityEntity, String permit) {
         for(Flyweight f : list){
             //遞歸調用
             if(f.match(securityEntity, permit)){
                return true;
             }
         }
         return false;
      }
}
  1. 在繼續(xù)實現(xiàn)之前,先來準備測試數(shù)據(jù)揽祥,也就是TestDB讽膏,需要有一些改變

首先是授權數(shù)據(jù)要區(qū)分是單條的授權,還是組合的授權拄丰,這個在每條授權數(shù)據(jù)后面添加一個標識來描述

然后增加一個描述組合數(shù)據(jù)的記錄府树,使用一個Map來存放

具體的示例代碼如下:

/**
   * 供測試用,在內存中模擬數(shù)據(jù)庫中的值
   */
public class TestDB {
      /**
       * 用來存放單獨授權數(shù)據(jù)的值
       */
      public static Collection<String> colDB = new ArrayList<String>();
      /**
       * 用來存放組合授權數(shù)據(jù)的值料按,
       * key為組合數(shù)據(jù)的id奄侠,value為該組合包含的多條授權數(shù)據(jù)的值
       */
      public static Map<String,String[]> mapDB = new HashMap<String,String[]>();
   
      static{
         //通過靜態(tài)塊來填充模擬的數(shù)據(jù),增加一個標識來表明是否組合授權數(shù)據(jù)
         colDB.add("張三,人員列表,查看,1");
         colDB.add("李四,人員列表,查看,1");
         colDB.add("李四,操作薪資數(shù)據(jù),,2");
     
         mapDB.put("操作薪資數(shù)據(jù)",new String[]{"薪資數(shù)據(jù),查看","薪資數(shù)據(jù),修改"});
     
         //增加更多的授權數(shù)據(jù)
         for(int i=0;i<3;i++){
             colDB.add("張三"+i+",人員列表,查看,1");
         }
      }
}
  1. 享元工廠不需要變化载矿,這里就不去贅述了

  2. 接下來該實現(xiàn)安全管理的類了,這個類相當于享元模式的Client角色,這次在這個類里面验靡,不單純使用共享的享元對象能真,它還會使用不需要共享的享元對象。

主要的變化集中在queryByUser方法里面逢勾,原本只是通過享元工廠來獲取共享的享元對象即可牡整,但是這次還需要在這里創(chuàng)建不需要共享的享元對象。示例代碼如下:

public class SecurityMgr {
      private static SecurityMgr securityMgr = new SecurityMgr();
      private SecurityMgr(){     
      }
      public static SecurityMgr getInstance(){
         return securityMgr;
      }
      /**
       * 在運行期間敏沉,用來存放登錄人員對應的權限果正,
       * 在Web應用中,這些數(shù)據(jù)通常會存放到session中
       */
      private Map<String,Collection<Flyweight>> map = new HashMap<String,Collection<Flyweight>>();
      /**
       * 模擬登錄的功能
       * @param user 登錄的用戶
       */
      public void login(String user){
         //登錄時就需要把該用戶所擁有的權限盟迟,從數(shù)據(jù)庫中取出來秋泳,放到緩存中去
         Collection<Flyweight> col = queryByUser(user);
         map.put(user, col);
      }
      /**
       * 判斷某用戶對某個安全實體是否擁有某權限
       * @param user 被檢測權限的用戶
       * @param securityEntity 安全實體
       * @param permit 權限
       * @return true表示擁有相應權限,false表示沒有相應權限
       */
      public boolean hasPermit(String user,String securityEntity,String permit){
         Collection<Flyweight> col = map.get(user);
         System.out.println("現(xiàn)在測試"+securityEntity+"的"+permit+"權限攒菠,map.size="+map.size());
         if(col==null || col.size()==0){
             System.out.println(user+"沒有登錄或是沒有被分配任何權限");
             return false;
         }
         for(Flyweight fm : col){
             //輸出當前實例迫皱,看看是否同一個實例對象
             System.out.println("fm=="+fm);
             if(fm.match(securityEntity, permit)){
                return true;
             }
         }
         return false;
      }  
      /**
       * 從數(shù)據(jù)庫中獲取某人所擁有的權限
       * @param user 需要獲取所擁有的權限的人員
       * @return 某人所擁有的權限
       */
      private Collection<Flyweight> queryByUser(String user){
         Collection<Flyweight> col = new ArrayList<Flyweight>();
         for(String s : TestDB.colDB){
             String ss[] = s.split(",");
             if(ss[0].equals(user)){
                Flyweight fm = null;
                if(ss[3].equals("2")){
                    //表示是組合
                    fm = new UnsharedConcreteFlyweight();
                    //獲取需要組合的數(shù)據(jù)
                    String tempSs[] = TestDB.mapDB.get(ss[1]);
                    for(String tempS : tempSs){
                       Flyweight tempFm = FlyweightFactory.getInstance().getFlyweight(tempS);
                       //把這個對象加入到組合對象中
                       fm.add(tempFm);
                    }
                }else{
                    fm = FlyweightFactory.getInstance().getFlyweight(ss[1]+","+ss[2]);
                }
            
                col.add(fm);
             }
         }
         return col;
      }  
}
  1. 客戶端測試沒有太大的變化,增加一條測試“李四對薪資數(shù)據(jù)的修改權限”,示例代碼如下:
 public class Client {
      public static void main(String[] args) throws Exception{
         //需要先登錄卓起,然后再判斷是否有權限
         SecurityMgr mgr = SecurityMgr.getInstance();
         mgr.login("張三");
         mgr.login("李四");   
         boolean f1 = mgr.hasPermit("張三","薪資數(shù)據(jù)","查看");
         boolean f2 = mgr.hasPermit("李四","薪資數(shù)據(jù)","查看");
         boolean f3 = mgr.hasPermit("李四","薪資數(shù)據(jù)","修改");
     
         System.out.println("f1=="+f1);
         System.out.println("f2=="+f2);
         System.out.println("f3=="+f3);
     
         for(int i=0;i<3;i++){
             mgr.login("張三"+i);
             mgr.hasPermit("張三"+i,"薪資數(shù)據(jù)","查看");
         }
      }
}

可以運行測試一下和敬,看看效果,結果示例如下:

現(xiàn)在測試薪資數(shù)據(jù)的查看權限戏阅,map.size=2
fm==cn.javass.dp.flyweight.example4.AuthorizationFlyweight@12dacd1
現(xiàn)在測試薪資數(shù)據(jù)的查看權限昼弟,map.size=2
fm==cn.javass.dp.flyweight.example4.AuthorizationFlyweight@12dacd1
fm==cn.javass.dp.flyweight.example4.UnsharedConcreteFlyweight@1ad086a
現(xiàn)在測試薪資數(shù)據(jù)的修改權限,map.size=2
fm==cn.javass.dp.flyweight.example4.AuthorizationFlyweight@12dacd1
fm==cn.javass.dp.flyweight.example4.UnsharedConcreteFlyweight@1ad086a
f1==false
f2==true
f3==true
現(xiàn)在測試薪資數(shù)據(jù)的查看權限奕筐,map.size=3
fm==cn.javass.dp.flyweight.example4.AuthorizationFlyweight@12dacd1
現(xiàn)在測試薪資數(shù)據(jù)的查看權限舱痘,map.size=4
fm==cn.javass.dp.flyweight.example4.AuthorizationFlyweight@12dacd1
現(xiàn)在測試薪資數(shù)據(jù)的查看權限,map.size=5
fm==cn.javass.dp.flyweight.example4.AuthorizationFlyweight@12dacd1
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末离赫,一起剝皮案震驚了整個濱河市芭逝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渊胸,老刑警劉巖旬盯,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翎猛,居然都是意外死亡胖翰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門办成,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泡态,“玉大人,你說我怎么就攤上這事迂卢∧诚遥” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵而克,是天一觀的道長靶壮。 經常有香客問我,道長员萍,這世上最難降的妖魔是什么腾降? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮碎绎,結果婚禮上螃壤,老公的妹妹穿的比我還像新娘。我一直安慰自己筋帖,他們只是感情好奸晴,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著日麸,像睡著了一般寄啼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天墩划,我揣著相機與錄音涕刚,去河邊找鬼。 笑死乙帮,一個胖子當著我的面吹牛杜漠,可吹牛的內容都是我干的。 我是一名探鬼主播蚣旱,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼碑幅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了塞绿?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤恤批,失蹤者是張志新(化名)和其女友劉穎异吻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喜庞,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡诀浪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了延都。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雷猪。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晰房,靈堂內的尸體忽然破棺而出求摇,到底是詐尸還是另有隱情,我是刑警寧澤殊者,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布与境,位于F島的核電站,受9級特大地震影響猖吴,放射性物質發(fā)生泄漏摔刁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一海蔽、第九天 我趴在偏房一處隱蔽的房頂上張望共屈。 院中可真熱鬧,春花似錦党窜、人聲如沸拗引。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寺擂。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間怔软,已是汗流浹背垦细。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挡逼,地道東北人括改。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像家坎,于是被迫代替她去往敵國和親嘱能。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容

  • 1.初識享元模式 運用共享技術有效地支持大量細粒度的對象虱疏。 Flyweight:享元接口惹骂,通過這個接口flywei...
    王偵閱讀 718評論 0 0
  • 【學習難度:★★★★☆,使用頻率:★☆☆☆☆】直接出處:享元模式梳理和學習:https://github.com/...
    BruceOuyang閱讀 520評論 0 0
  • 一. Java基礎部分.................................................
    wy_sure閱讀 3,793評論 0 11
  • 設計模式分類 總體來說設計模式分為三大類:創(chuàng)建型模式做瞪,共五種:工廠方法模式对粪、抽象工廠模式、單例模式装蓬、建造者模式著拭、原...
    lifeline丿毅閱讀 1,197評論 0 2
  • 在閻宏博士的《JAVA與模式》一書中開頭是這樣描述享元(Flyweight)模式的: Flyweight在拳擊比賽...
    聶叼叼閱讀 553評論 1 0