享元模式 FlyWeight 結構型 設計模式(十五)

来源:https://www.cnblogs.com/noteless/archive/2018/12/06/10074969.html
-Advertisement-
Play Games

享元模式是一種很常用的思想,核心就是共用,剝離事物的內部狀態與外部狀態,本文對享元模式 FlyWeight進行了簡單介紹,並且給出了該模式的意圖,結構,並且介紹了享元模式的兩種應用方式單純享元模式與複合享元模式,並且給出來了Java的示例代碼。享元模式的特點,結構,使用場景,都可以在本文中找到。 ...


享元模式(FlyWeight)  ”取“共用”之意,“”取“單元”之意。 image_5c087c0a_b27

意圖

運用共用技術,有效的支持大量細粒度的對象。

意圖解析

面向對象的程式設計中,一切皆是對象,這也就意味著系統的運行將會依賴大量的對象。 試想,如果對象的數量過多,勢必會增加系統負擔,導致運行的代價過高。 下麵看兩個小例子理解下 1.)有一首歌曲叫做《大舌頭》 其中有一句歌詞“說說說說 說你愛我 我我我我 說不出口” image_5c087c0a_3c7e 如果使用面向對象的編程方式對這段歌詞進行描述,假設一個漢字表示一個對象,你會怎麼做? 你會用七個還是十六個對象進行表示? image_5c087c0a_1241   2.)有一個文本編輯器軟體,對於每一個字元使用對象進行表示 當打開一篇有很多重覆字元的、數萬字的文章時,你會使用幾個對象進行表示? 如果仍舊採用每個字元占用一個對象,系統勢必崩潰,必然需要共用對象   上面的兩個例子中,都涉及到重覆對象的概念  而享元模式的意圖就是如此,將重覆的對象進行共用以達到支持大量細粒度對象的目的。 如果不進行共用,如例2中描述的那樣,一篇數萬字元的文章將會產生數萬個對象,這將是一場可怕的災難。 image_5c087c0a_7394 flyweight意為輕量級 在我們當前的場景下,寓意為通過共用技術,輕量級的---也就是記憶體占用更小 本質就是“共用”所以中文翻譯過來多稱之為享元 簡言之,享元模式就是要“共用對象” 對於Java語言,我們熟悉的String,就是享元模式的運用 String是不可變對象,一旦創建,將不會改變 在JVM內部,String對象都是共用的 如果一個系統中的兩個String對象,包含的字元串相同,只會創建一個String對象提供給兩個引用 從而實現String對象的共用(new 的對象是兩個不同的) image_5c087c0a_149c 享元模式又不僅僅是簡單的“共用對象” 上面的兩個小例子中,對於文字中的重覆字元 可以通過共用對象的方式,對某些對象進行共用,從而減少記憶體開銷。 考慮下圖中的情景,這裡面所有的“你”字,到底是不是同樣的?
  • 是,因為全部都是漢字“你”
  • 不是,因為儘管都是漢字“你”,但是他們的字體,顏色,字型大小,卻又明顯不同,所以不是同樣的
image_5c087c0b_388b   如果將字體、顏色、字型大小,作為“你”這個漢字的狀態 是不是可以認為:他們都是一樣的漢字,但是他們卻又具有不同的狀態? 其實享元模式不僅僅用來解決大量重覆對象的共用問題,還能夠用來解決相似對象的問題。 享元對象能夠共用的關鍵在於:區分對象的內部狀態外部狀態 內部狀態是存儲在享元對象內部的,並且不會隨環境的變化而有所改變。 比如上面的漢字“你”,無論在任何情況下,漢字“你”,始終是“你”,不會變成“她” 所以說享元模式解決共用問題,本質是共用內部狀態 外部狀態是隨外部環境變化而變化,不能共用的狀態。 享元對象的外部狀態通常由客戶端保存,在必要的時候在傳遞到享元對象內部 比如上面漢字“你”的字體、顏色、字型大小就是外部狀態。   

小結

享元模式就是為了避免系統中出現大量相同或相似的對象,同時又不影響客戶端程式通過面向對象的方式對這些對象進行操作 享元模式通過共用技術,實現相同或相似對象的重用 比如文編編輯器讀取文本 在邏輯上每一個出現的字元都有一個對象與之對應,然而在物理上它們卻共用同一個享元對象 在享元模式中,存儲這些共用實例對象的地方通常叫做享元池(Flyweight  Pool) 享元模式可以結合String的intern()方法一起進行理解   通過區分了內部狀態和外部狀態,就可以將相同內部狀態的對象存儲在池中,池中的對象可以實現共用 需要的時候將對象從池中取出,實現對象的復用 通過向取出的對象註入不同的外部狀態,進而得到一些列相似的對象 而這些看似各異的對象在記憶體中,僅僅存儲了一份,大大節省了空間,所以說很自然的命名為“flyweight”輕量級

享元工廠

通過對意圖的認識,可以認為,享元模式其實就是對於“程式中會出現的大量重覆或者相似對象”的一種“重構” 當然,你應該是在設計之初就想到這個問題,而不是真的出現問題後再去真的重構 比如,你想要設計“字元”這種對象時,就應該考慮到他的“大量””重覆““相似”的特點 所以需要分析出字元的內部狀態,與外部狀態 上面也提到對於享元對象,通過享元池進行管理 對於池的管理通常使用工廠模式,藉助於工廠類對享元池進行管理 用戶需要對象時,通過工廠類獲取 工廠提供一個存儲在享元池中的已創建的對象實例,或者創建一個新的實例

示例代碼

針對於上面的例子,漢字“你”作為內部狀態,可以進行共用 “顏色”作為外部狀態,由客戶端保存傳遞 創建字元類 Character、漢字字元類ChineseCharacter、顏色類Color以及工廠類CharacterFactory Color含有顏色屬性,通過構造方法設置,getter方法獲取
package flyweight;
public class Color {
    public String Color;
    public Color(String color) {
        this.Color = color;
    }
    public String getColor() {
        return Color;
    }
}

 

Character 抽象的字元類,用於描述字元
package flyweight;
public abstract class Character {
    public abstract String getValue();
     
    public void display(Color color) {
        System.out.println("字元: " + getValue() + " ,顏色: " + color.getColor());
    }
}
漢字字元類,為了簡化,直接設置value為漢字“你”

 

package flyweight;
public class ChineseCharacter extends Character {
    @Override
    public String getValue() {
        return "你";
    }
}
CharacterFactory字元工廠類 通過單例模式創建工廠 內部HashMap用於存儲字元,並且提供獲取方法 為了簡化程式,初始就創建了一個漢字字元“你”存儲於字元中
package flyweight;
import java.util.HashMap;
public class CharacterFactory {
    /**
    * 單例模式 餓漢式創建
    */
    private static CharacterFactory instance = new CharacterFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
    private CharacterFactory() {
        Character character = new ChineseCharacter();
        hm.put("你", character);
    }
    /**
    * 單例全局訪問介面獲取工廠
    */
    public static CharacterFactory getInstance() {
        return instance;
    }
     
    /**
    * 根據key獲取池中的對象
    */
    public Character getCharacter(String key) {
        return (Character) hm.get(key);
    }
}
測試代碼 image_5c087c0b_6037 示例中,我們通過工廠,從享元池中獲取了三個漢字字元“你”。 通過 == 可以看得出來,他們都是同一個對象 在分別調用他們的display方法時,在客戶端(此處為我們的Test main方法)中創建,並且傳遞給享元對象 通過方法參數的形式進行外部狀態的設置。 image_5c087c0b_2137   CharacterFactory 單例模式,返回自身實例 CharacterFactory內部維護Character的享元池 Character 依賴Color ChineseCharacter是Character的實現類

結構

將上面的示例轉換為標準的享元模式的名稱 image_5c087c0b_1edf   抽象享元角色 FlyWeight 所有具體享元類的超類,為這些類規定了需要實現的公共介面 外部狀態可以通過業務邏輯方法的參數形式傳遞進來 具體享元角色ConcreteFlyWeight 實現抽象享元角色所規定的的介面 需要保存內部狀態,而且,內部狀態必須與外部狀態無關 從而才能使享元對象可以在系統內共用 享元工廠角色 FlyWeightFactory 負責創建和管理享元角色,也就是維護享元池 必須保證享元對象可以被系統適當的共用 接受客戶端的請求 如果有適當符合要求的享元對象,則返回 如果沒有一個適當的享元對象,則創建 客戶端角色Client
客戶端角色維護了對所有享元對象的引用
image_5c087c0b_69dd 需要保存維護享元對象的外部狀態,然後通過享元對象的業務邏輯方法作為參數形式傳遞 image_5c087c0b_6b32

分類

單純享元模式

在上面的結構中,如果所有的ConcreteFlyWeight都可以被共用 也就是所有的FlyWeight子類都可以被共用,那就是所有的享元對象都可以被共用 這種形式被稱之為單純享元模式 單純享元代碼
package flyweight.simple;
public abstract class FlyWeight {
/**
* 抽象的業務邏輯方法,接受外部狀態作為參數
*/
abstract public void operation(String outerState);
}
package flyweight.simple;
public class ConcreteFlyWeight extends FlyWeight {
    private String innerState = null; 
    public ConcreteFlyWeight(String innerState) {
        this.innerState = innerState;
    } 
    /**
    * 外部狀態作為參數傳遞
    */
    @Override
    public void operation(String outerState) {
        System.out.println("innerState = " + innerState + " outerState = " + outerState);
    }
}
package flyweight.simple;
import java.util.HashMap;
public class FlyWeightFactory {
    /**
    * 單例模式 餓漢式創建
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
     
    private FlyWeightFactory() {
    }
    /**
    * 單例全局訪問介面獲取工廠
    */
    public static FlyWeightFactory getInstance() {
    return instance;
    }
    /**
    * 根據innerState獲取池中的對象
    * 存在返回,不存在創建並返回
    */
    public FlyWeight getFylWeight(String innerState) {
        if(hm.containsKey(innerState)){
        return (FlyWeight) hm.get(innerState);
        }else{
        FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
        hm.put(innerState,flyWeight);
        return flyWeight;
        }
    }
}

 

package flyweight.simple;
public class Test {
public static void main(String[] args){
FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");

System.out.println(flyWeight1);
System.out.println(flyWeight2);
System.out.println(flyWeight3);
System.out.println();

flyWeight1.operation("outer state XXX");
flyWeight2.operation("outer state YYY");
flyWeight3.operation("outer state ZZZ");
}
}

 

  image_5c087c0b_75b6   

複合享元模式

與單純享元模式對應的是複合享元模式 單純享元模式中,所有的享元對象都可以共用 複合享元模式中,則並不是所有的ConcreteFlyWeight都可以被共用 也就是說:不是所有的享元對象都可以被共用 實際上,並不是所有的FlyWeight子類都需要被共用 FlyWeight介面使的可以進行共用,但是沒有任何必要 強制必須共用 實踐中,UnsharedConcreteFlyWeight對象通常將ConcreteFlyWeight對象作為子節點 image_5c087c0b_4a4f 與單純享元模式相比,僅僅是擁有了不可共用的具體子類 而且,這個子類往往是應用了組合模式,將ConcreteFlyWeight對象作為子節點 複合享元角色UnsharedConcreteFlyWeight
複合享元角色,也就是不可共用的,也被稱為 不可共用的享元對象
但是一個複合享元對象可以分解為多個本身是單純享元對象的組合
這些單純的享元對象就又是可以共用的
  複合享元代碼 將簡單模式中的示例代碼進行改造 FlyWeight不變
package flyweight.composite;
public abstract class FlyWeight {
/**
* 抽象的業務邏輯方法,接受外部狀態作為參數
*/
abstract public void operation(String outerState);
}
ConcreteFlyWeight不變
package flyweight.composite;
public class ConcreteFlyWeight extends FlyWeight {
private String innerState = null;
public ConcreteFlyWeight(String innerState) {
this.innerState = innerState;
}
/**
* 外部狀態作為參數傳遞
*/
@Override
public void operation(String outerState) {
System.out.println("innerState = " + innerState + " outerState = " + outerState);
}
}
新增加不共用的子類也就是組合的享元子類 內部使用list 維護單純享元模式對象,提供add方法進行添加 提供operation操作
package flyweight.composite;
import java.util.ArrayList;
import java.util.List;
public class UnsharedConcreateFlyWeight extends FlyWeight {
private String innerState = null;
public UnsharedConcreateFlyWeight(String innerState) {
this.innerState = innerState;
}

private List<FlyWeight> list = new ArrayList<>();
public void add(FlyWeight flyWeight) {
list.add(flyWeight);
}
@Override
public void operation(String outerState) {
for (FlyWeight flyWeight:list) {
flyWeight.operation(outerState);
    }
}
}
FlyWeightFactory工廠類進行改造 新增加public UnsharedConcreateFlyWeight getCompositeFylWeight(String state)  用於獲得組合享元對象
package flyweight.composite;
 
import java.util.HashMap;
 
public class FlyWeightFactory {
    /**
    * 單例模式 餓漢式創建
    */
    private static FlyWeightFactory instance = new FlyWeightFactory();
     
    /**
    * 使用HashMap管理享元池
    */
    private HashMap<String, Object> hm = new HashMap<>();
     
    /**
    * 管理複合享元對象
    */
    private HashMap<String, Object> compositeHm = new HashMap<>();
     
    private FlyWeightFactory() {
    }
     
    /**
    * 單例全局訪問介面獲取工廠
    */
    public static FlyWeightFactory getInstance() {
        return instance;
    }
     
    /**
    * 根據innerState獲取池中的對象
    * 存在返回,不存在創建並返回
    */
    public FlyWeight getFylWeight(String innerState) {
        if(hm.containsKey(innerState)){
            return (FlyWeight) hm.get(innerState);
        }else{
            FlyWeight flyWeight = new ConcreteFlyWeight(innerState);
            hm.put(innerState,flyWeight);
            return flyWeight;
        }
    }
     
    /**
    * 根據innerState獲取池中的對象
    * 存在返回,不存在創建並返回
    */
    public UnsharedConcreateFlyWeight getCompositeFylWeight(String state) {
        if(compositeHm.containsKey(state)){
            return (UnsharedConcreateFlyWeight) compositeHm.get(state);
        }else{
            UnsharedConcreateFlyWeight flyWeight = new UnsharedConcreateFlyWeight(state);
            compositeHm.put(state,flyWeight);
            return flyWeight;
        }
    }
 
}
測試類也進行改造
package flyweight.composite;
public class Test {
public static void main(String[] args){
    FlyWeightFactory flyWeightFactory = FlyWeightFactory.getInstance();
    FlyWeight flyWeight1 = flyWeightFactory.getFylWeight("First");
    FlyWeight flyWeight2 = flyWeightFactory.getFylWeight("Second");
    FlyWeight flyWeight3 = flyWeightFactory.getFylWeight("First");
     
    System.out.println(flyWeight1);
    System.out.println(flyWeight2);
    System.out.println(flyWeight3);
     
    System.out.println("###########################################");
     
    flyWeight1.operation("outer state XXX");
    flyWeight2.operation("outer state YYY");
    flyWeight3.operation("outer state ZZZ");
    System.out.println("###########################################");
    UnsharedConcreateFlyWeight compositeFlyWeight = flyWeightFactory.getCompositeFylWeight("composite");
    compositeFlyWeight.add(flyWeight1);
    compositeFlyWeight.add(flyWeight2);
    compositeFlyWeight.operation("composite out state OOO");
    }
}
image_5c087c0b_5108   測試程式在原來的基礎上,新獲得了一個組合享元對象 然後將兩個單純享元對象添加到組合享元對象中 然後調用operation,通過列印信息可以看得出來 不同的單純享元對象,他們卻有了一致的外部狀態 image_5c087c0b_4c7c   所以使用複合享元模式的一個常用目的就是: 多個內部狀態不同的單純享元對象,擁有一致的外部狀態 這種場景下,就可以考慮使用複合享元模式

使用場景

如果有下列情況,則可以考慮使用享元模式
  • 應用程式中使用了大量的對象
  • 大量的對象明顯增加了程式的存儲運行開銷
  • 對象可以提取出內部狀態,並且可以分離外部狀態
使用享元模式有一點需要特別註意:應用程式運行不依賴這些對象的身份 換句話說這些對象是不做區分的,適用於“在客戶端眼裡,他們都是一樣的”這種場景 比如單純的使用對象的方法,而不在意對象是否是創建而來的,否則如果客戶端鑒別對象的身份(equals),當他們是同一個對象時將會出現問題  

總結

享元模式的核心就是共用 共用就需要找準內部狀態,以及分離外部狀態外部狀態由客戶端維護,在必要時候,通過參數的形式註入到享元對象中 在有大量重覆或者相似對象的場景下,都可以考慮到享元模式   而且為了達到共用的目的,需要通過工廠對象進行控制 只有通過工廠來維護享元池才能達到共用的目的,如果任意創建使用則勢必不能很好地共用   享元模式大大的減少了對象的創建,降低了系統所需要的記憶體空間 但是由於將狀態分為內部狀態和外部狀態,而外部狀態是分離的,那麼狀態的讀取必然會增大開銷 所以說享元模式是時間換空間   如果確定需要使用享元模式,如果對於多個內部狀態不同的享元對象,希望他們擁有一致性的外部狀態 那麼就可以考慮複合享元模式,複合享元模式是與合成模式的結合。   原文地址:享元模式 FlyWeight 結構型 設計模式(十五)
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 計算屬性就是模板內的表達式非常便利,但是設計它們的初衷是用於簡單運算的。 一、什麼是計算屬性 模板內的表達式非常便利,但是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。例如: 這裡的表達式包含3個操作,並不是很清晰,所以遇到複雜邏輯時應該使用Vue特帶的計算屬性com ...
  • element ui源碼的版本是2.4.9 pagination.js pager.vue ...
  • 結構型設計模式藉助於組合或者繼承以整體結構的形式提供更強大的功能,他們之間有很多點非常相似,本文對七個結構型設計模式進行了對比,代理模式,裝飾器模式,享元模式,橋接模式,外觀模式,組合模式,適配器模式他們之間的異同點,差異點進行了分析,有助於更好地理解學習各種模式。 ...
  • 組合模式也叫合成模式,用來描述部分與整體的關係。 定義: 將對象組合成樹形結構以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。 組合模式類圖如下所示。 組合模式提供以下3個角色: 抽象構件(Component)角色:定義參加組合對象的共有方法和屬性,規範一些預設的行為接 ...
  • 橋梁模式(Bridge Pattern)也稱橋接模式,是一種簡單的、不常使用的設計模式。 定義: 將抽象和實現解耦,使得兩者可以獨立地變化。 橋梁模式類圖如下所示。 橋梁模式有以下4個角色: 抽象化(Abstraction)角色:定義出該角色的行為,同時保存一個對實現化角色的引用,該角色一般是抽象類 ...
  • 外觀模式(Facade Pattern)也叫門面模式,是一種比較常用也是非常簡單的設計模式。 定義: 要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行。外觀模式提供一個高層次的介面,使得子系統更易使用。 外觀模式具有以下兩個角色。 外觀(Facade)角色:客戶端可以調用該角色的方法,該 ...
  • 系統架構設計師-軟體水平考試高級-理論-項目管理。其中涉及範圍管理,時間管理,成本管理,質量管理,配置管理,風險管理等。 ...
  • 享元模式(Flyweight Pattern)是池技術的重要實現方式,可以降低大量重覆的、細粒度的類在記憶體中的開銷。 定義: 使用共用對象可有效地支持大量的細粒度對象。 以共用的方式高效地支持大量的細粒度對象。享元對象能做到共用的關鍵是區分內部狀態(Internal State)和外部狀態(Exte ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...