qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          跟屌絲大哥學習設計模式--享元模式

          四、舉例

          這里就以去餐館吃飯為例詳細的說明下享元模式的使用方式。去菜館點菜吃飯的過程大家一定都是輕車熟路了,這里就不贅述。在例子中我使用了一個list來存放外蘊狀態和內蘊狀態的對應關系,而且提供了查詢每個客人點菜情況的方法。內蘊狀態在這里代表了菜肴的種類,而外蘊狀態就是每盤菜肴的點菜人。
            A 讓我們先來看看單純享元模式的實現吧。

              先看下抽象享元角色的定義:GoF對享元模式的描述是:運用共享技術有效地支持大量細粒度的對象。
          Flyweight模式是構造型模式之一,它通過與其他類似對象共享數據來減小內存占用。也就是說在一個系統中如果有多個相同的對象,那么只共享一份就可 以了,不必每個都去實例化一個對象。在Flyweight模式中,由于要產生各種各樣的對象,所以在Flyweight(享元)模式中常出現 Factory模式。Flyweight的內部狀態是用來共享的,Flyweight factory負責維護一個對象存儲池(Flyweight Pool)來存放內部狀態的對象。為了調用方便,FlyweightFactory類一般使用Singleton模式實現。Flyweight模式是一個 提高程序效率和性能的模式,會大大加快程序的運行速度。

          Flyweight模式的有效性很大程度上取決于如何使用它以及在何處使用它。當以下情況成立時使用Flyweight模式:
          1 一個應用程序使用了大量的對象。
          2 完全由于使用大量的對象,造成很大的存儲開銷。
          3 對象的大多數狀態都可以變為外部狀態。
          4 如果刪除對象以外的狀態那么可以用相對較少的共享對象取代很多組對象。
          5 應用程序不依賴于對象標識。
          其結構圖如下所示:

          比如說,一個咖啡店有幾種口味的咖啡(拿鐵、摩卡、卡布奇諾等等),如果這家店接到分訂單要幾十杯咖啡。那么顯然咖啡的口味就可以設置成共享的,而不必為每一杯單獨生成。代碼實現如下:

          import java.util.*;

          public abstract class Order {
          // 執行賣出動作
          public abstract void sell();
          }

          public class FlavorOrder extends Order {
          public String flavor;

          // 獲取咖啡口味
          public FlavorOrder(String flavor) {
             this.flavor = flavor;
          }

          @Override
          public void sell() {
             // TODO Auto-generated method stub
             System.out.println("賣出一份" + flavor + "的咖啡。");
          }
          }

          public class FlavorFactory {
          private Map<String, Order> flavorPool = new HashMap<String, Order>();

          // 靜態工廠,負責生成訂單對象
          private static FlavorFactory flavorFactory = new FlavorFactory();

          private FlavorFactory() {
          }

          public static FlavorFactory getInstance() {
             return flavorFactory;
          }

          public Order getOrder(String flavor) {
             Order order = null;

             if (flavorPool.containsKey(flavor)) {// 如果此映射包含指定鍵的映射關系,則返回 true
              order = flavorPool.get(flavor);

             } else {
              order = new FlavorOrder(flavor);
              flavorPool.put(flavor, order);
             }
             return order;
          }

          public int getTotalFlavorsMade() {
             return flavorPool.size();
          }
          }

          public class Client {
          // 客戶下的訂單
          private static List<Order> orders = new ArrayList<Order>();

          // 訂單對象生成工廠
          private static FlavorFactory flavorFactory;

          // 增加訂單
          private static void takeOrders(String flavor) {
             orders.add(flavorFactory.getOrder(flavor));
          }

          public static void main(String[] args) {
             // 訂單生成工廠
             flavorFactory = FlavorFactory.getInstance();

             // 增加訂單
             takeOrders("摩卡");
             takeOrders("卡布奇諾");
             takeOrders("香草星冰樂");
             takeOrders("香草星冰樂");
             takeOrders("拿鐵");
             takeOrders("卡布奇諾");
             takeOrders("拿鐵");
             takeOrders("卡布奇諾");
             takeOrders("摩卡");
             takeOrders("香草星冰樂");
             takeOrders("卡布奇諾");
             takeOrders("摩卡");
             takeOrders("香草星冰樂");
             takeOrders("拿鐵");
             takeOrders("拿鐵");

             // 賣咖啡
             for (Order order : orders) {
              order.sell();
             }

             // 打印生成的訂單java對象數量
             System.out.println("\n客戶一共買了 " + orders.size() + " 杯咖啡! ");

             // 打印生成的訂單java對象數量
             System.out.println("共生成了 " + flavorFactory.getTotalFlavorsMade()
               + " 個 FlavorOrder java對象! ");
          }
          }

          賣出一份摩卡的咖啡。
          賣出一份卡布奇諾的咖啡。
          賣出一份香草星冰樂的咖啡。
          賣出一份香草星冰樂的咖啡。
          賣出一份拿鐵的咖啡。
          賣出一份卡布奇諾的咖啡。
          賣出一份拿鐵的咖啡。
          賣出一份卡布奇諾的咖啡。
          賣出一份摩卡的咖啡。
          賣出一份香草星冰樂的咖啡。
          賣出一份卡布奇諾的咖啡。
          賣出一份摩卡的咖啡。
          賣出一份香草星冰樂的咖啡。
          賣出一份拿鐵的咖啡。
          賣出一份拿鐵的咖啡。

          客戶一共買了 15 杯咖啡!
          共生成了 4 個 FlavorOrder java對象!

          正如輸入結果對比所示,把口味共享極大減少了對象數目,減小了內存消耗。

          優缺點:
          1)享元模式使得系統更加復雜。為了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯復雜化。
          2)享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。

          一、引子

          讓我們先來復習下javaString類型的特性:String類型的對象一旦被創造就不可改變;當兩個String對象所包含的內容相同的時候,JVM只創建一個String對象對應這兩個不同的對象引用。讓我們來證實下著兩個特性吧(如果你已經了解,請跳過直接閱讀第二部分)。

          先來驗證下第二個特性:

          public class TestPattern {

                 public static void main(String[] args){

                        String n = "I Love Java";

                        String m = "I Love Java";            

                        System.out.println(n==m);

                 }

          }

          這段代碼會告訴你n==mtrue,這就說明了在JVMnm兩個引用了同一個String對象(如果你還分不清== equals的區別的話,請先確認)。

          那么接著驗證下第一個特性:

          在系統輸出之前加入一行代碼“m = m + "hehe";”,這時候n==m結果為false,為什么剛才兩個還是引用相同的對象,現在就不是了呢?原因就是在執行后添加語句時,m指向了一個新創建的String對象,而不是修改引用的對象。

          呵呵,說著說著就差點跑了題,并不是每個String的特性都跟我們今天的主題有關的。
                String類型的設計避免了在創建N多的String對象時產生的不必要的資源損耗,可以說是享元模式應用的范例,那么讓我們帶著對享元的一點模糊的認識開始,來看看怎么在自己的程序中正確的使用享元模式!

          注:使用String類型請遵循《Effective Java》中的建議。

           二、定義與分類

          享元模式英文稱為“Flyweight Pattern”,我非常感謝將Flyweight Pattern翻譯成享元模式的那位強人,因為這個詞將這個模式使用的方式明白得表示了出來;如果翻譯成為羽量級模式或者蠅量級模式等等,雖然可以含蓄的表現出使用此模式達到的目的,但是還是沒有抓住此模式的關鍵。

          享元模式的定義為:采用一個共享來避免大量擁有相同內容對象的開銷。這種開銷中最常見、直觀的就是內存的損耗。享元模式以共享的方式高效的支持大量的細粒度對象。

          在名字和定義中都體現出了共享這一個核心概念,那么怎么來實現共享呢?要知道每個事物都是不同的,但是又有一定的共性,如果只有完全相同的事物才能共享,那么享元模式可以說就是不可行的;因此我們應該盡量將事物的共性共享,而又保留它的個性。為了做到這點,享元模式中區分了內蘊狀態和外蘊狀態。內蘊狀態就是共性,外蘊狀態就是個性了。

          注:共享的對象必須是不可變的,不然一變則全變(如果有這種需求除外

          內蘊狀態存 儲在享元內部,不會隨環境的改變而有所不同,是可以共享的;外蘊狀態是不可以共享的,它隨環境的改變而改變的,因此外蘊狀態是由客戶端來保持(因為環境的 變化是由客戶端引起的)。在每個具體的環境下,客戶端將外蘊狀態傳遞給享元,從而創建不同的對象出來。至于怎樣來維護客戶端保持的外蘊狀態和享元內部保持 的內蘊狀態的對應關系,你先不用擔心這個問題,我們后面會涉及到的。

          我們引用《Java與模式》中的分類,將享元模式分為:單純享元模式和復合享元模式。在下一個小節里面我們將詳細的講解這兩種享元模式。

          三、結構

          先從簡單的入手,看看單純享元模式的結構。

          1)        抽象享元角色:為具體享元角色規定了必須實現的方法,而外蘊狀態就是以參數的形式通過此方法傳入。在Java中可以由抽象類、接口來擔當。

          2)        具體享元角色:實現抽象角色規定的方法。如果存在內蘊狀態,就負責為內蘊狀態提供存儲空間。

          3)        享元工廠角色:負責創建和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵!

          4)        客戶端角色:維護對所有享元對象的引用,而且還需要存儲對應的外蘊狀態。

          來用類圖來形象地表示出它們的關系吧(對類圖的了解可以參看我關于類圖的blog)。


          怎么咋看咋像簡單工廠模式呢!沒錯,可以說結構型的單純享元模式和創建型的簡單工廠模式實現上非常相似,但是它的重點或者用意卻和工廠模式截然不同。工廠模式的使用主要是為了使系統不依賴于實現得細節(見《深入淺出工廠模式》);而在享元模式的主要目的如前面所述:采用共享技術來避免大量擁有相同內容對象的開銷。正所謂“舊瓶裝新酒”阿!

             再來看看復合享元模式的結構。

          1)        抽象享元角色:為具體享元角色規定了必須實現的方法,而外蘊狀態就是以參數的形式通過此方法傳入。在Java中可以由抽象類、接口來擔當。

          2)        具體享元角色:實現抽象角色規定的方法。如果存在內蘊狀態,就負責為內蘊狀態提供存儲空間。

          3)        復合享元角色:它所代表的對象是不可以共享的,并且可以分解成為多個單純享元對象的組合。

          4)        享元工廠角色:負責創建和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵!

          5)        客戶端角色:維護對所有享元對象的引用,而且還需要存儲對應的外蘊狀態。

          統比一下單純享元對象和復合享元對象,里面只多出了一個復合享元角色,但是它的結構就發生了很大的變化。我們還是使用類圖來表示下:



          你 也許又納悶了,這個也似曾相逢!單看左半部,和簡單工廠模式類似;再看右半部,怎么這么像合成模式呢(請參看關于合成模式的文章或者期待我的《深入淺出合 成模式》)!合成模式用在此處就是為了將具體享元角色和復合享元角色同等對待和處理,通過將享元模式與合成模式組合在一起,可以確保復合享元中所包含的每 個單純享元都具有相同的外蘊狀態,而這些單純享元的內蘊狀態往往是不同的。

          四、舉例

          這里就以去餐館吃飯為例詳細的說明下享元模式的使用方式。去菜館點菜吃飯的過程大家一定都是輕車熟路了,這里就不贅述。在例子中我使用了一個list來存放外蘊狀態和內蘊狀態的對應關系,而且提供了查詢每個客人點菜情況的方法。內蘊狀態在這里代表了菜肴的種類,而外蘊狀態就是每盤菜肴的點菜人。
            A 讓我們先來看看單純享元模式的實現吧。
              先看下抽象享元角色的定義:

          interface Menu

          {

                 //規定了實現類必須實現設置內外關系的方法

                 public void setPersonMenu(String person , List list); 

          //規定了實現類必須實現查找外蘊狀態對應的內蘊狀態的方法

                 public List findPersonMenu(String person, List list);

          }

          這便是具體享元角色了:

          class PersonMenu implements Menu

          {

                 private String dish ;

                 //在構造方法中給內蘊狀態附值

                 public PersonMenu(String dish){

                        this.dish = dish ;

                 }

                 public synchronized void setPersonMenu(String person , List list)

                 {

                        list.add(person);

                        list.add(dish);

                 }

                 public List findPersonMenu(String person, List list)

                 {

                        List dishList = new ArrayList();

                        Iterator it = list.iterator();

                        while(it.hasNext())

                        {

                               if(person.equals((String)it.next()))

                                      dishList.add(it.next());

                        }

                        return dishList ;

                 }

          }

          享元工廠角色,這可是關鍵所在,大家注意看!

          class FlyweightFactory

          {

                 private Map menuList = new HashMap();

                 private static FlyweightFactory factory = new FlyweightFactory();

                 //這里還使用了單例模式,來使工廠對象只產生一個工廠實例

                 private FlyweightFactory(){}

                 public static FlyweightFactory getInstance()

                 {

                        return factory ;

                 }

          //這就是享元模式同工廠模式的不同所在??!

                 public synchronized Menu factory(String dish)

                 {

          //判斷如果內蘊狀態已經存在就不再重新生成,而是使用原來的,否則就重新生成 

                        if(menuList.containsKey(dish))

                        {

                               return (Menu)menuList.get(dish);

                        }else{

                               Menu menu = new PersonMenu(dish);

                               menuList.put(dish,menu);

                               return menu;

                        }

                 }

          //來驗證下是不是真的少產生了對象

                 public int getNumber()

                 {

                        return menuList.size();

                 }

          }

          我們使用客戶程序來試驗下吧。

          class Client

          {

                 private static FlyweightFactory factory ;

                 public static void main(String[] args)

                 {

                        List list1 = new ArrayList();

                        factory = FlyweightFactory.getInstance();

                        Menu list = factory.factory("尖椒土豆絲");

                        list.setPersonMenu("ai92",list1);

                        list = factory.factory("紅燒肉");

                        list.setPersonMenu("ai92",list1);

                        list = factory.factory("地三鮮");

                        list.setPersonMenu("ai92",list1);

                        list = factory.factory("地三鮮");

                        list.setPersonMenu("ai92",list1);

                        list = factory.factory("紅燜鯉魚");

                        list.setPersonMenu("ai92",list1);

                        list = factory.factory("紅燒肉");

                        list.setPersonMenu("ai921",list1);

                        list = factory.factory("紅燜鯉魚");

                        list.setPersonMenu("ai921",list1);

                        list = factory.factory("地三鮮");

                        list.setPersonMenu("ai921",list1);

                        System.out.println(factory.getNumber());

                       

                        List list2 = list.findPersonMenu("ai921",list1);

                        Iterator it = list2.iterator();

                        while(it.hasNext())

                        {

                               System.out.println(" "+it.next());

                        }

                 }

          }

           

              這 樣便使用單純享元模式實現了這些功能,但是你是不是發現一個人點了好幾樣菜的時候是不是使用很不方便?而這種情況正好符合復合享元模式的使用條件:復合享 元中所包含的每個單純享元都具有相同的外蘊狀態,而這些單純享元的內蘊狀態往往是不同的。由于復合享元模式不能共享,所以不存在什么內外狀態對應的問題。 所以在復合享元類中我們不用實現抽象享元對象中的方法,因此這里采用的是透明式的合成模式。
              那么下面我就使用復合享元模式在上例的基礎上來實現一下。
              首先要實現一個復合享元角色:
              class PersonMenuMuch implements Menu
              {
                   private Map MenuList = new HashMap();
                   public PersonMenuMuch(){}
                   //增加一個新的單純享元對象
                   public void add(String key , Menu menu)
                   {
                    MenuList.put(key , menu);
                   }
                  //兩個無為的方法
                   public synchronized void setPersonMenu(String person , List list)
                   { }
                   public List findPersonMenu(String person, List list)
                   {
                    List nothing = null ;
                    return nothing ;
                   }
              }
              在工廠方法中添加一個方法,實現重載。
              public Menu factory(String[] dish)
               {
                    PersonMenuMuch menu = new PersonMenuMuch();
                    String key = null ;
                    for(int i=0 ; i<dish.length ; i++)
                    {
                         key = dish[i];
                         menu.add(key , this.factory(key));//調用了單純享元角色的工廠方法
                    }
                    return menu ;
               }
                也許我的例子舉的不太恰當,但是基本上也能看出單純享元模式和復合享元模式在實現上的特點,如果這個目的達到了那就忘了這個糟糕的例子吧(不要讓它成了你深入理解享元模式的障礙),讓我們來分析下這兩種模式吧。
                先從復雜度上來講,復合享元模式顯而易見是比單純享元模式復雜的。
                再從享元模式的關鍵——共享,來分析:復合享元模式在共享上面是沒有達到預期的效果,可以說是沒有起到共享的目的。雖然對于它內部包含的單純享元角色來說還是能夠起到共享的作用,但是復合享元角色中一個內蘊狀態和對象使用了兩個Map來保存,這肯定是不會節省什么空間和對象個數的。所以我認為復合享元模式是違背享元模式初衷的。因此我們應該盡量使用單純享元模式。
                
                在程序中你也許注意到,我對內蘊外蘊狀態對應關系的保持是采用一個list表來做的,這僅僅是個舉例,你完全可以采用各種能達到目的的方式來完成。這一點 也說明在享元模式中僅提供給我們怎么來吧一個對象的狀態分開來達到共享,而對于關系的維護它是不關心的,也不是這個模式涉及的內容。

                這樣我就把享元模式使用一個例子詳細的講解了一下。如果還是不太明白的話請回味下前面的定義與結構。只有兩者結合才能很好的體會到享元模式的用意。

          五、使用優缺點

          享元模式優點就在于它能夠大幅度的降低內存中對象的數量;而為了做到這一步也帶來了它的缺點:它使得系統邏輯復雜化,而且在一定程度上外蘊狀態影響了系統的速度。

          所以一定要切記使用享元模式的條件:

          1)        系統中有大量的對象,他們使系統的效率降低。

          2)        這些對象的狀態可以分離出所需要的內外兩部分。

          外 蘊狀態和內蘊狀態的劃分以及兩者關系的對應也是非常值得重視的。只有將內外劃分妥當才能使內蘊狀態發揮它應有的作用;如果劃分失誤,在最糟糕的情況下系統 中的對象是一個也不會減少的!兩者的對應關系的維護和查找也是要花費一定的空間(當然這個比起不使用共享對象要小得多)和時間的,可以說享元模式就是使用 時間來換取空間的。在Gof的書中是使用了B樹來進行對應關系查找優化。


          六、總結
                也許你要長嘆一聲:這個享元模式未必太復雜了吧!這點是不得不承認的,也許由于它的復雜,實際應用也不是很多,這是我們更加無法看清他的真面目了。不過享 元模式并不是雞肋,它的精髓——共享是對我們系統優化非常有好處的,而且這種思想已經別越來越多的應用,這應該就算是享元模式的應用了吧。如果你已經領會 到了享元模式的精髓,那么也就是掌握了享元模式了!

              匆匆學完了享元模式,不知道理解上有沒有紕漏,希望大家能指正出來,一起共同進步!其實我一直想使用一個實際系統中或者實踐中的例子來講解享元模式,可是畢竟自己的工作經驗太少了!!于是想在網上找一些靈感來,可是狂搜一陣子也沒有發現什么,于是就又落俗套的使用了一個比喻的例子。如果您對此深有體會的話,還煩請不吝賜教??!

           Flyweight在拳擊比賽中指最輕量級。享元模式以共享的方式高效的支持大量的細粒度對象。是一種結構模式,處理類和對象之間的組合,避免大量擁有相同內容的小類的開銷(入耗費內存),使大家共享一個類(元類)。
          享元模式是一個提高程序效率和性能的模式,會大大加快程序的運行速度,應用場合很多:比如從一個數據庫中讀取一系列的字符串,這些字符串中有很多重復的,這時候就可以講重復的字符轉存貯在Flyweight池中。
                又比如說一個文本系統,每個字母定義一個對象,大小寫在一塊一共定義52個對象。如果在一個1M的文本中有那么多的字母,如果每個字母都要定義一個對象的話內存占有量就太大了。如果每個字母都共享一個對象的話那就只用定義52個對象,用到的時候直接來拿就節省了很多資源。
          public class Test {
              public static void main(String[] args)
          {              String a = "abc";  
               String b = "abc";
                  String  c=“ab”+”c”;
                 System.out.println(a==b);
                   System.out.println(a==c);          }
          }

           享元模式可以分成單純享元模式和復合享元模式兩種形式。
                 在單純的享元模式中,所有的享元對象都是可以共享的。
                 在單純享元模式中,所有的享元對象都是單純享元對象,也就是說都是可以直接共享的。還有一種較為復雜的情況,將一些單純享元使用合成模式加以復合,形成復合享元對象。這樣的復合享元對象本身不能共享,但是它們可以分解成單純享元對象,而后者則可以共享。
          享元對象能做到共享的關鍵是區分內蘊狀態(Internal State)和外蘊狀態(External State)。   一個內蘊狀態是存儲在享元對象內部的,并且是不會隨環境的改變而有所不同。因此,一個享元可以具有內蘊狀態并可以共享。   一個外蘊狀態是隨環境的改變而改變的、不可以共享的。享元對象的外蘊狀態必須由客戶端保存,并在享元對象被創建之后,在需要使用的時候再傳入到享元對象內部。外蘊狀態不可以影響享元對象的內蘊狀態,它們是相互獨立的。
          抽象享元角色:是所有具體享元角色的超類需要實現公共接口,可以通過這個導入外蘊狀態。
          具體享元角色:實現抽象香園角色的接口,并負責為內蘊狀態提供空間,使得內蘊與周圍的環境無關。
          享元工廠角色:負責創建和管理享元角色。工廠必須保證里面的對象可以被系統適當的時候共享,當客戶端對象需要調用一個共享對象的時候,工廠角色要去查看自己是否有這個對象,有就直接拿出去用,沒有就立刻在創建一個。
          客戶端角色:需要維護一個對所有對象的引用,并且要自行存儲所有享元對象的外蘊狀態。



          抽象享元角色:是所有具體享元角色的超類需要實現公共接口,可以通過這個導入外蘊狀態。
          具體享元角色:實現抽象香園角色的接口,并負責為內蘊狀態提供空間,使得內蘊與周圍的環境無關。
          復合享元角色:不可以被共享但是可以分為很多可以被共享的單元。
          享元工廠角色:負責創建和管理享元角色。工廠必須保證里面的對象可以被系統適當的時候共享,當客戶端對象需要調用一個共享對象的時候,工廠角色要去查看自己是否有這個對象,有就直接拿出去用,沒有就立刻在創建一個。
          客戶端角色:需要維護一個對所有對象的引用,并且要自行存儲所有享元對象的外蘊狀態。


          享元模式的優缺點   享元模式的優點在于它大幅度地降低內存中對象的數量。但是,它做到這一點所付出的代價也是很高的:   ●  享元模式使得系統更加復雜。為了使對象可以共享,需要將一些狀態外部化,這使得程序的邏輯復雜化。   ●  享元模式將享元對象的狀態外部化,而讀取外部狀態使得運行時間稍微變長。

           享元模式以共享的方式高效地支持大量的細粒度對象。

                在面向對象的程序設計語言看來,一切事務都被描述成對象(Object)。對象擁有狀態(屬性)和行為(方法),我們將具有相同行為的對象抽象為類(Class),類可以被看作只保留行為的對象模板,類可以在運行時被重新賦予狀態數據從而形成了對象。

                在運行時,對象占用一定的內存空間用來存儲狀態數據。如果不作特殊的處理,盡管是由同一個類生成的兩個對象,而且這兩個對象的的狀態數據完 全相同,但在內存中還是會占用兩份空間,這樣的情況對于程序的功能也許并沒有影響,但如果把狀態相同的同一類對象在內存中進行合并,必然會大大減少存儲空 間的浪費。

                舉一個現實中的例子,某淘寶店經營一款暢銷女式皮鞋,每天需要處理大量的訂單信息,在訂單中需要注明客戶購買的皮鞋信息,我們將皮鞋產品抽象出來:

          class Shoe{
          String color;//顏色
          int size;//尺寸
          String position;//庫存位置
          public String getColor() {
          return color;
          }
          public void setColor(String color) {
          this.color = color;
          }
          public int getSize() {
          return size;
          }
          public void setSize(int size) {
          this.size = size;
          }
          public String getPosition() {
          return position;
          }
          public void setPosition(String position) {
          this.position = position;
          }
          }

             正如上面的代碼所描述,皮鞋分為顏色、尺寸和庫存位置三項狀態數據。其中顏色和尺寸為皮鞋的自然狀態,我們稱之為對象內部狀態,這些狀態數據只與對象本身 有關,不隨外界環境的改變而發生變化。再來看庫存位置,我們將這個狀態稱為對象的外部狀態,外部狀態與對象本身無必然關系,外部狀態總是因為外界環境的改 變而變化,也就是說外部狀態是由外界環境來決定的。在本例中,皮鞋今天放在A倉庫,明天可能放在B倉庫,但無論存放在哪個倉庫,同一只皮鞋就是同一只皮 鞋,它的顏色和尺寸不會隨著存放位置的不同而發生變化。

                享元模式的核心思想就是將內部狀態相同的對象在存儲時進行緩存。也就是說同一顏色同一尺寸的皮鞋,我們在內存中只保留一份實例,在訪問對象時,我們訪問的其實是對象緩存的版本,而不是每次都重新生成對象。

                享元模式仍然允許對象具有外部屬性,由于我們訪問的始終是對象緩存的版本,所以我們在使用對象前,必須將外部狀態重新注入對象。由于享元模式禁止生成新的對象,所以在使用享元模式時,通常伴隨著工廠方法的應用。我們來看下面的例子:

          class ShoeFactory {

          Collection<Shoe> shoes = new ArrayList<Shoe>();

          Shoe getSheo(String color, int size, String position) {
          //首先在緩存中查找對象
          for (Shoe shoe : shoes) {
          if (shoe.getColor() == color && shoe.getSize() == size) {
          //在緩存中命中對象后還原對象的外部屬性
          shoe.setPosition(position);
          return shoe;
          }
          }
          //如果緩存未命中則新建對象并加入緩存
          Shoe shoe = new Shoe();
          shoe.setColor(color);
          shoe.setSize(size);
          shoe.setPosition(position);
          shoes.add(shoe);
          return shoe;
          }
          }



           通過ShoeFactory工廠,我們每次拿到的皮鞋都是緩存的版本,如果緩存中沒有我們需要的對象,則新創建對象然后加入緩存中。注意上例中對象的外部屬性position是如何注回對象的。

                當我們在自己的業務場景中應用享元模式時,一定要注意分清對象的內部狀態和外部狀態,享元模式強調緩存的版本只能包含對象的內部狀態。

                事實上,Java中的String和Integer類都是享元模式的應用的例子,String類內部對所有的字符串對象進行緩存,相同的字符串在內存中只會保留一個版本。類似的,Integer類在內部對小于255的整數也進行了緩存。

                享元模式在企業級架構設計中應用的例子比比皆是,現代大型企業級應用中不可或缺的緩存體系也正是在享元模式的基礎上逐步完善和發展起來的。

           

           

           

           

          posted on 2012-12-14 18:02 順其自然EVO 閱讀(5132) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2012年12月>
          2526272829301
          2345678
          9101112131415
          16171819202122
          23242526272829
          303112345

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 美姑县| 莎车县| 民勤县| 巫溪县| 左贡县| 和顺县| 常德市| 房山区| 南昌县| 德格县| 宝清县| 枞阳县| 凤凰县| 姚安县| 尚义县| 兰西县| 海宁市| 鄂尔多斯市| 太白县| 平顶山市| 鲁甸县| 封丘县| 乐安县| 九台市| 寿阳县| 铜梁县| 丰县| 镇原县| 亚东县| 句容市| 乌苏市| 奉新县| 吉木乃县| 利川市| 海城市| 宜春市| 会宁县| 徐水县| 宜丰县| 星子县| 茶陵县|